diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..af94704ce --- /dev/null +++ b/.editorconfig @@ -0,0 +1,97 @@ +# Core EditorConfig Options # +root = true +# All files +[*] +indent_style = space +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = false +charset = utf-8-bom +# .NET Coding Conventions # +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +# C# Coding Conventions # +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +# C# Formatting Rules # +# New line preferences +csharp_new_line_before_open_brace = false +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_members_in_anonymous_types = false +csharp_new_line_between_query_expression_clauses = false +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true diff --git a/Controls/Graph.cs b/Controls/Graph.cs new file mode 100644 index 000000000..e63a2b4ab --- /dev/null +++ b/Controls/Graph.cs @@ -0,0 +1,334 @@ +using System; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Windows.Forms; +namespace FallGuysStats { + public class Graph : PictureBox { + private DataTable dataSource; + [Browsable(false)] + public DataTable DataSource { + get { return dataSource; } + set { + int ccount = dataSource != null ? dataSource.Columns.Count : 0; + int ncount = value != null ? value.Columns.Count : 0; + dataSource = value; + if (dataSource != null) { + if (ccount != ncount) { + yColumns = new bool[dataSource.Columns.Count]; + bool found = false; + for (int i = dataSource.Columns.Count - 1; i >= 0; i--) { + yColumns[i] = i != XColumn && !found; + if (yColumns[i]) + found = true; + } + RefreshColors(); + } + } + Invalidate(); + } + } + [DefaultValue(0)] + public int XColumn { get; set; } + private bool[] yColumns; + [Browsable(false)] + public bool[] YColumns { get { return yColumns; } } + [DefaultValue(true)] + public bool DrawPoints { get; set; } + [DefaultValue(0)] + private int opacity; + public int Opacity { get { return opacity; } set { opacity = value; Invalidate(); } } + [DefaultValue(typeof(Color), "Transparent")] + private Color backColor; + public Color BackgroundColor { get { return backColor; } set { backColor = value; Invalidate(); } } + [Browsable(false)] + public new Image InitialImage { get; set; } + [Browsable(false)] + public new Image ErrorImage { get; set; } + [Browsable(false)] + public new Color BackColor { get { return base.BackColor; } set { base.BackColor = value; } } + private Brush[] brushes; + private Pen[] pens; + private int closeRowIndex, closeColumnIndex; + private Point lastMousePosition; + private static Color[] Colors = new Color[] { Color.Black, Color.Red, Color.Green, Color.Blue }; + + public Graph() { + closeRowIndex = -1; + closeColumnIndex = -1; + opacity = 0; + backColor = Color.Transparent; + XColumn = 0; + DrawPoints = true; + } + + public void RefreshColors() { + if (dataSource == null) { return; } + brushes = new Brush[dataSource.Columns.Count]; + pens = new Pen[dataSource.Columns.Count]; + foreach (DataColumn col in dataSource.Columns) { + brushes[col.Ordinal] = new SolidBrush(Colors[col.Ordinal]); + pens[col.Ordinal] = new Pen(brushes[col.Ordinal]); + } + } + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + closeRowIndex = -1; + closeColumnIndex = -1; + Invalidate(); + } + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + + if (dataSource == null || dataSource.DefaultView.Count == 0) { return; } + + int w = Width; int h = Height; + decimal xmax = decimal.MinValue; decimal xmin = decimal.MaxValue; decimal ymax = decimal.MinValue; decimal ymin = decimal.MaxValue; + Type yType = null; + Type xType = dataSource.Columns[XColumn].DataType; + bool visible = false; + //Determine min / max + foreach (DataRowView row in dataSource.DefaultView) { + decimal newx = GetValue(row[XColumn]); + if (newx > xmax) { xmax = newx; } + if (newx < xmin) { xmin = newx; } + foreach (DataColumn col in dataSource.Columns) { + if (!yColumns[col.Ordinal]) { continue; } + visible = true; + yType = col.DataType; + decimal newy = GetValue(row[col.Ordinal]); + if (newy > ymax) { ymax = newy; } + if (newy < ymin) { ymin = newy; } + } + } + if (!visible) { return; } + ymin = 0; + //Get bounds + int wmax = 0; int wmin = 0; int hmin = 0; int hmax = 0; + CalculateMinMax(xmin, xmax, xType, ymin, ymax, yType, ref wmin, ref wmax, ref hmin, ref hmax); + int mod = (int)ymax % 8; + ymax += mod == 0 ? 0 : 8 - mod; + //Get inital values + int closeInd = 0; + int closeTemp = 0; + int close = int.MaxValue; + int closeIndY = 0; + int i = 0; + foreach (DataRowView row in dataSource.DefaultView) { + int x = NormalizeX(GetValue(row[XColumn]), xmin, xmax, wmin, wmax) - e.X; + closeTemp = x * x; + foreach (DataColumn col in dataSource.Columns) { + if (!yColumns[col.Ordinal]) { continue; } + int y = NormalizeY(GetValue(row[col.Ordinal]), ymin, ymax, hmin, hmax) - e.Y; + y = closeTemp + y * y; + if (close > y) { + close = y; + closeIndY = col.Ordinal; + closeInd = i; + } + } + i++; + } + if (closeRowIndex != closeInd || closeColumnIndex != closeIndY) { + closeRowIndex = closeInd; + closeColumnIndex = closeIndY; + } + lastMousePosition = e.Location; + Invalidate(); + } + protected override void OnPaintBackground(PaintEventArgs e) { + base.OnPaintBackground(e); + Graphics g = e.Graphics; + + if (Parent != null) { + base.BackColor = Color.Transparent; + int index = Parent.Controls.GetChildIndex(this); + + for (int i = Parent.Controls.Count - 1; i > index; i--) { + Control c = Parent.Controls[i]; + if (c.Bounds.IntersectsWith(Bounds) && c.Visible) { + Bitmap bmp = new Bitmap(c.Width, c.Height, g); + c.DrawToBitmap(bmp, c.ClientRectangle); + + g.TranslateTransform(c.Left - Left, c.Top - Top); + g.DrawImageUnscaled(bmp, Point.Empty); + g.TranslateTransform(Left - c.Left, Top - c.Top); + bmp.Dispose(); + } + } + g.FillRectangle(new SolidBrush(Color.FromArgb(Opacity * 255 / 100, BackgroundColor)), this.ClientRectangle); + } else { + g.Clear(Color.Transparent); + g.FillRectangle(new SolidBrush(Color.FromArgb(Opacity * 255 / 100, BackgroundColor)), this.ClientRectangle); + } + } + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + if (dataSource == null || dataSource.DefaultView.Count == 0) { + if (DesignMode) { + DataTable dt = new DataTable(); + dt.Columns.Add("X", typeof(int)); + dt.Columns.Add("Y", typeof(int)); + dt.Rows.Add(1, 1); + dt.Rows.Add(2, 8); + dt.Rows.Add(3, 5); + dt.Rows.Add(4, 3); + dt.Rows.Add(5, 10); + dt.Rows.Add(6, 8); + dt.Rows.Add(7, 3); + DataSource = dt; + } else { + return; + } + } + + int w = Width; int h = Height; + decimal xmax = decimal.MinValue; decimal xmin = decimal.MaxValue; decimal ymax = decimal.MinValue; decimal ymin = decimal.MaxValue; + Type yType = null; + Type xType = dataSource.Columns[XColumn].DataType; + bool visible = false; + //Determine min / max + foreach (DataRowView row in dataSource.DefaultView) { + decimal newx = GetValue(row[XColumn]); + if (newx > xmax) { xmax = newx; } + if (newx < xmin) { xmin = newx; } + foreach (DataColumn col in dataSource.Columns) { + if (!yColumns[col.Ordinal]) { continue; } + visible = true; + yType = col.DataType; + decimal newy = GetValue(row[col.Ordinal]); + if (newy > ymax) { ymax = newy; } + if (newy < ymin) { ymin = newy; } + } + } + if (!visible) { + ymax = 10; + ymin = 0; + } + ymin = 0; + + //Draw labels + int wmax = 0; int wmin = 0; int hmin = 0; int hmax = 0; + CalculateMinMax(xmin, xmax, xType, ymin, ymax, yType, ref wmin, ref wmax, ref hmin, ref hmax); + int mod = (int)ymax % 8; + ymax += mod == 0 ? 0 : 8 - mod; + float sz = DefaultFont.SizeInPoints; + Graphics g = e.Graphics; + decimal y8 = (ymax - ymin) / (decimal)8.0; decimal x8 = (xmax - xmin) / (decimal)8.0; + double h8 = (double)(hmax - hmin) / (double)8.0; double w8 = (double)(wmax - wmin) / (double)8.0; + Pen bp = new Pen(Color.Black, 1); + bp.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + g.DrawLine(bp, wmin, 0, wmin, hmax); + g.DrawLine(bp, wmin, hmax, w - 1, hmax); + bp.Color = Color.FromArgb(30, 0, 0, 0); + for (int i = 0; i <= 8; i++) { + string xval = GetRepresentation(xType, x8 * i + xmin, xmax - xmin); + float xsz = TextRenderer.MeasureText(xval, DefaultFont).Width; + float tx = (float)(w8 * i + wmin); + g.DrawString(xval, DefaultFont, Brushes.Black, tx + 2 - xsz / (float)2.0, hmax + 2); + if (i > 0) + g.DrawLine(bp, tx, 0, tx, hmax - 1); + float ty = (float)(h - 3 * sz - h8 * i); + g.DrawString((y8 * i + ymin).ToString("0"), DefaultFont, Brushes.Black, (float)2, ty); + if (i > 0) + g.DrawLine(bp, wmin + 1, ty + sz, w - 1, ty + sz); + } + + //Draw data + DataRowView init = dataSource.DefaultView[0]; + int x = NormalizeX(GetValue(init[XColumn]), xmin, xmax, wmin, wmax); + int[] y = new int[brushes.Length]; + foreach (DataColumn col in dataSource.Columns) { + if (!yColumns[col.Ordinal]) { continue; } + y[col.Ordinal] = NormalizeY(GetValue(init[col.Ordinal]), ymin, ymax, hmin, hmax); + if (DrawPoints) { g.FillRectangle(brushes[col.Ordinal], x - 2, y[col.Ordinal] - 2, 4, 4); } + } + + bool start = true; + foreach (DataRowView row in dataSource.DefaultView) { + if (start) { start = false; continue; } + int newx = NormalizeX(GetValue(row[XColumn]), xmin, xmax, wmin, wmax); + foreach (DataColumn col in dataSource.Columns) { + if (!yColumns[col.Ordinal]) { continue; } + int newy = NormalizeY(GetValue(row[col.Ordinal]), ymin, ymax, hmin, hmax); + if (DrawPoints) { g.FillRectangle(brushes[col.Ordinal], newx - 2, newy - 2, 4, 4); } + g.DrawLine(pens[col.Ordinal], x, y[col.Ordinal], newx, newy); + y[col.Ordinal] = newy; + } + x = newx; + } + + if (closeRowIndex >= 0) { + g.DrawLine(Pens.Black, lastMousePosition, new Point(NormalizeX(GetValue(dataSource.DefaultView[closeRowIndex][XColumn]), xmin, xmax, wmin, wmax), NormalizeY(GetValue(dataSource.DefaultView[closeRowIndex][closeColumnIndex]), ymin, ymax, hmin, hmax))); + string val = dataSource.Columns[closeColumnIndex].ColumnName + " = " + dataSource.DefaultView[closeRowIndex][closeColumnIndex].ToString() + " (" + GetRepresentation(xType, GetValue(dataSource.DefaultView[closeRowIndex][XColumn]), xmax - xmin) + ")"; + Size size = TextRenderer.MeasureText(val, DefaultFont); + int px = lastMousePosition.X + size.Width > w ? w - size.Width : lastMousePosition.X; + int py = lastMousePosition.Y - size.Height < 0 ? 0 : lastMousePosition.Y - size.Height; + g.DrawString(val, DefaultFont, Brushes.Black, px, py); + } + e.Dispose(); + } + private void CalculateMinMax(decimal xmin, decimal xmax, Type xType, decimal ymin, decimal ymax, Type yType, ref int wmin, ref int wmax, ref int hmin, ref int hmax) { + int ysz = TextRenderer.MeasureText(GetRepresentation(yType, ymin, ymax - ymin), DefaultFont).Width; + int ysz2 = TextRenderer.MeasureText(GetRepresentation(yType, ymax, ymax - ymin), DefaultFont).Width; + ysz = ysz > ysz2 ? ysz : ysz2; + ysz += TextRenderer.MeasureText("00", DefaultFont).Width; + int xsz = TextRenderer.MeasureText(GetRepresentation(xType, xmax, xmax - xmin), DefaultFont).Width; + int xsz2 = TextRenderer.MeasureText(GetRepresentation(xType, xmin, xmax - xmin), DefaultFont).Width / 2; + ysz = ysz > xsz2 ? ysz : xsz2; + float sz = DefaultFont.SizeInPoints; + decimal xdiff = xmax - xmin; decimal ydiff = ymax - ymin; + wmax = (int)(Width - (double)xsz / 2.0); + wmin = ysz; + hmin = (int)sz; + hmax = (int)(Height - 2 * sz); + } + private int NormalizeX(decimal x, decimal xmin, decimal xmax, int wmin, int wmax) { + double point = xmax - xmin == 0 ? 0 : (double)(x - xmin) / (double)(xmax - xmin); + return (int)((wmax - wmin) * point + wmin); + } + private int NormalizeY(decimal y, decimal ymin, decimal ymax, int hmin, int hmax) { + double point = ymax - ymin == 0 ? 0 : (double)(y - ymin) / (double)(ymax - ymin); + return (int)(hmax - (double)(hmax - hmin) * point); + } + private decimal GetValue(object value) { + if (value == null) { + return 0; + } else if (value is DateTime) { + return ((DateTime)value).Ticks; + } else if (value is int) { + return (int)value; + } else if (value is long) { + return (long)value; + } else if (value is double) { + return new decimal((double)value); + } else if (value is float) { + return new decimal((float)value); + } else if (value is short) { + return (short)value; + } else if (value is byte) { + return (byte)value; + } else { + return 0; + } + } + private string GetRepresentation(Type t, decimal value, decimal range) { + if (t == typeof(DateTime)) { + if (TimeSpan.FromTicks((long)range).Days > 0) { + return new DateTime((long)value).ToString("yy-MM-dd"); + } else { + return new DateTime((long)value).ToString("HH:mm"); + } + } else if (t == typeof(int)) { + return ((int)value).ToString(); + } else if (t == typeof(long)) { + return ((long)value).ToString(); + } else if (t == typeof(byte)) { + return ((byte)value).ToString(); + } else if (t == typeof(short)) { + return ((int)value).ToString(); + } + return value.ToString(); + } + } +} \ No newline at end of file diff --git a/Grid.cs b/Controls/Grid.cs similarity index 60% rename from Grid.cs rename to Controls/Grid.cs index 0d3ded4ee..7bc7bdee2 100644 --- a/Grid.cs +++ b/Controls/Grid.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; @@ -13,10 +14,10 @@ public sealed class Grid : DataGridView { private ContextMenuStrip cMenu; private IContainer components; private SaveFileDialog saveFile; - private ToolStripMenuItem exportItem; + private ToolStripMenuItem exportItemCSV, exportItemHTML, exportItemBBCODE, exportItemMD; private bool IsEditOnEnter, readOnly; private bool? allowUpdate, allowNew, allowDelete; - + public Dictionary Orders = new Dictionary(StringComparer.OrdinalIgnoreCase); public Grid() { InitializeComponent(); AllowUserToAddRows = false; @@ -37,6 +38,24 @@ public Grid() { ColumnHeadersDefaultCellStyle.SelectionBackColor = Color.Cyan; ColumnHeadersDefaultCellStyle.SelectionForeColor = Color.Black; } + public SortOrder GetSortOrder(string columnName) { + SortOrder sortOrder; + Orders.TryGetValue(columnName, out sortOrder); + + if (sortOrder == SortOrder.None) { + Columns[columnName].HeaderCell.SortGlyphDirection = SortOrder.Ascending; + Orders[columnName] = SortOrder.Ascending; + return SortOrder.Ascending; + } else if(sortOrder == SortOrder.Ascending){ + Columns[columnName].HeaderCell.SortGlyphDirection = SortOrder.Descending; + Orders[columnName] = SortOrder.Descending; + return SortOrder.Descending; + } else { + Columns[columnName].HeaderCell.SortGlyphDirection = SortOrder.None; + Orders[columnName] = SortOrder.None; + return SortOrder.None; + } + } public DataGridViewRow CloneWithValues(DataGridViewRow row) { DataGridViewRow clonedRow = (DataGridViewRow)row.Clone(); for (int i = 0; i < row.Cells.Count; i++) { @@ -165,33 +184,65 @@ public bool HasFocus(Control activeControl) { } return false; } - private void exportItem_Click(object sender, EventArgs e) { + private string EscapeQuotes(string value, char escapeCharacter = ',') { + if (!string.IsNullOrEmpty(value) && value.IndexOf(escapeCharacter) >= 0) { + return $"\"{value}\""; + } + return value; + } + + #region Export event handlers + private void exportItemCSV_Click(object sender, EventArgs e) { + ExportCsv(); + } + + private void exportItemHTML_Click(object sender, EventArgs e) { + ExportHtml(); + } + + private void exportItemBBCODE_Click(object sender, EventArgs e) { + ExportBbCode(); + } + + private void exportItemMD_Click(object sender, EventArgs e) { + ExportMarkdown(); + } + #endregion + + #region Export methods + public void ExportCsv() { try { saveFile.Filter = "CSV files|*.csv"; if (saveFile.ShowDialog() == DialogResult.OK) { Encoding enc = Encoding.GetEncoding("windows-1252"); using (FileStream fs = new FileStream(saveFile.FileName, FileMode.Create)) { + List columns = GetSortedColumns(); + StringBuilder sb = new StringBuilder(); - foreach (DataGridViewColumn col in this.Columns) { - if (col.ValueType != null && col.Visible) { - sb.Append(col.Name).Append(","); - } + foreach (DataGridViewColumn col in columns) { + string header = string.IsNullOrEmpty(col.HeaderText) ? col.ToolTipText : col.HeaderText; + sb.Append(EscapeQuotes(header)).Append(","); } if (sb.Length > 0) { sb.Length = sb.Length - 1; } + sb.AppendLine(); byte[] bytes = enc.GetBytes(sb.ToString()); fs.Write(bytes, 0, bytes.Length); + foreach (DataGridViewRow row in this.Rows) { sb.Length = 0; - foreach (DataGridViewColumn col in this.Columns) { - if (!col.Visible) { continue; } - if (col.ValueType == typeof(string)) { - sb.Append("\"").Append(row.Cells[col.Name].Value.ToString()).Append("\","); - } else if (col.ValueType != null) { - sb.Append(row.Cells[col.Name].Value.ToString()).Append(","); + foreach (DataGridViewColumn col in columns) { + string formattedValue = row.Cells[col.Name].FormattedValue?.ToString(); + string tooltip = row.Cells[col.Name].ToolTipText; + + if (string.IsNullOrEmpty(tooltip) || row.Cells[col.Name].FormattedValueType == typeof(string)) { + sb.Append($"{EscapeQuotes(formattedValue)},"); + } else { + sb.Append($"{EscapeQuotes(tooltip)},"); } } if (sb.Length > 0) { sb.Length = sb.Length - 1; } + sb.AppendLine(); bytes = enc.GetBytes(sb.ToString()); fs.Write(bytes, 0, bytes.Length); @@ -199,11 +250,134 @@ private void exportItem_Click(object sender, EventArgs e) { fs.Flush(); fs.Close(); } + + MessageBox.Show(this, $"Saved CSV to {saveFile.FileName}", "Export", MessageBoxButtons.OK); + } + } catch (Exception ex) { + ControlErrors.HandleException(this, ex, false); + } + } + + public void ExportHtml() { + try { + List columns = GetSortedColumns(); + + StringBuilder sb = new StringBuilder(); + sb.Append(""); + foreach (DataGridViewColumn col in columns) { + string header = string.IsNullOrEmpty(col.HeaderText) ? col.ToolTipText : col.HeaderText; + sb.Append($""); + } + sb.AppendLine(""); + + foreach (DataGridViewRow row in this.Rows) { + sb.Append(""); + foreach (DataGridViewColumn col in columns) { + string formattedValue = row.Cells[col.Name].FormattedValue?.ToString(); + string tooltip = row.Cells[col.Name].ToolTipText; + + if (string.IsNullOrEmpty(tooltip) || row.Cells[col.Name].FormattedValueType == typeof(string)) { + sb.Append($""); + } else { + sb.Append($""); + } + } + sb.AppendLine(""); + } + sb.Append("
{header}
{formattedValue}{tooltip}
"); + Clipboard.SetText(sb.ToString(), TextDataFormat.Text); + + MessageBox.Show(this, "Saved Html to clipboard.", "Export", MessageBoxButtons.OK); + } catch (Exception ex) { + ControlErrors.HandleException(this, ex, false); + } + } + + public void ExportBbCode() { + try { + List columns = GetSortedColumns(); + + StringBuilder sb = new StringBuilder(); + sb.Append("[table][tr]"); + foreach (DataGridViewColumn col in columns) { + string header = string.IsNullOrEmpty(col.HeaderText) ? col.ToolTipText : col.HeaderText; + sb.Append($"[th]{header}[/th]"); + } + sb.Append("[/tr]"); + + foreach (DataGridViewRow row in this.Rows) { + sb.Append("[tr]"); + foreach (DataGridViewColumn col in columns) { + string formattedValue = row.Cells[col.Name].FormattedValue?.ToString(); + string tooltip = row.Cells[col.Name].ToolTipText; + + if (string.IsNullOrEmpty(tooltip) || row.Cells[col.Name].FormattedValueType == typeof(string)) { + sb.Append($"[td]{formattedValue}[/td]"); + } else { + sb.Append($"[td]{tooltip}[/td]"); + } + } + sb.Append("[/tr]"); + } + sb.Append("[/table]"); + Clipboard.SetText(sb.ToString(), TextDataFormat.Text); + + MessageBox.Show(this, "Saved BBCode to clipboard.", "Export", MessageBoxButtons.OK); + } catch (Exception ex) { + ControlErrors.HandleException(this, ex, false); + } + } + + public void ExportMarkdown() { + try { + List columns = GetSortedColumns(); + + StringBuilder sb = new StringBuilder(); + foreach (DataGridViewColumn col in columns) { + string header = string.IsNullOrEmpty(col.HeaderText) ? col.ToolTipText : col.HeaderText; + sb.Append($"|{header}"); + } + sb.AppendLine(); + foreach (DataGridViewColumn col in columns) { + sb.Append($"|---"); + } + sb.AppendLine(); + + foreach (DataGridViewRow row in this.Rows) { + foreach (DataGridViewColumn col in columns) { + string formattedValue = row.Cells[col.Name].FormattedValue?.ToString(); + string tooltip = row.Cells[col.Name].ToolTipText; + + if (string.IsNullOrEmpty(tooltip) || row.Cells[col.Name].FormattedValueType == typeof(string)) { + sb.Append($"|{formattedValue}"); + } else { + sb.Append($"|{tooltip}"); + } + } + sb.AppendLine(); } + + Clipboard.SetText(sb.ToString(), TextDataFormat.Text); + + MessageBox.Show(this, "Saved MarkDown to clipboard.", "Export", MessageBoxButtons.OK); } catch (Exception ex) { ControlErrors.HandleException(this, ex, false); } } + #endregion + + private List GetSortedColumns() { + List columns = new List(); + foreach (DataGridViewColumn col in this.Columns) { + if (!col.Visible || (string.IsNullOrEmpty(col.HeaderText) && string.IsNullOrEmpty(col.ToolTipText))) { continue; } + + columns.Add(col); + } + columns.Sort(delegate (DataGridViewColumn one, DataGridViewColumn two) { + return one.DisplayIndex.CompareTo(two.DisplayIndex); + }); + return columns; + } private void Grid_DataError(object sender, DataGridViewDataErrorEventArgs e) { e.ThrowException = false; } @@ -306,7 +480,10 @@ private void InitializeComponent() { this.components = new Container(); ComponentResourceManager resources = new ComponentResourceManager(typeof(Grid)); this.cMenu = new ContextMenuStrip(this.components); - this.exportItem = new ToolStripMenuItem(); + this.exportItemCSV = new ToolStripMenuItem(); + this.exportItemHTML = new ToolStripMenuItem(); + this.exportItemBBCODE = new ToolStripMenuItem(); + this.exportItemMD = new ToolStripMenuItem(); this.saveFile = new SaveFileDialog(); this.cMenu.SuspendLayout(); ((ISupportInitialize)(this)).BeginInit(); @@ -314,18 +491,49 @@ private void InitializeComponent() { // // cMenu // - this.cMenu.Items.AddRange(new ToolStripItem[] { this.exportItem }); + this.cMenu.Items.AddRange(new ToolStripItem[] { this.exportItemCSV, this.exportItemHTML, this.exportItemBBCODE, this.exportItemMD }); this.cMenu.Name = "contextMenu"; this.cMenu.Size = new Size(135, 48); // - // exportItem + // exportItemCSV + // + this.exportItemCSV.Name = "exportItemCSV"; + this.exportItemCSV.Size = new Size(134, 22); + this.exportItemCSV.Text = "Export CSV"; + this.exportItemCSV.ShowShortcutKeys = true; + this.exportItemCSV.Image = Properties.Resources.export; + this.exportItemCSV.ShortcutKeys = Keys.Control | Keys.S; + this.exportItemCSV.Click += new EventHandler(this.exportItemCSV_Click); + // + // exportItemHTML + // + this.exportItemHTML.Name = "exportItemHTML"; + this.exportItemHTML.Size = new Size(134, 22); + this.exportItemHTML.Text = "Export HTML"; + this.exportItemHTML.ShowShortcutKeys = true; + this.exportItemHTML.Image = Properties.Resources.export; + this.exportItemHTML.ShortcutKeys = Keys.Control | Keys.E; + this.exportItemHTML.Click += new EventHandler(this.exportItemHTML_Click); + // + // exportItemBBCODE + // + this.exportItemBBCODE.Name = "exportItemBBCODE"; + this.exportItemBBCODE.Size = new Size(134, 22); + this.exportItemBBCODE.Text = "Export BBCode"; + this.exportItemBBCODE.ShowShortcutKeys = true; + this.exportItemBBCODE.Image = Properties.Resources.export; + this.exportItemBBCODE.ShortcutKeys = Keys.Control | Keys.B; + this.exportItemBBCODE.Click += new EventHandler(this.exportItemBBCODE_Click); + // + // exportItemMD // - this.exportItem.Name = "exportItem"; - this.exportItem.Size = new Size(134, 22); - this.exportItem.Text = "&Export to CSV"; - this.exportItem.ShowShortcutKeys = true; - this.exportItem.ShortcutKeys = Keys.Control | Keys.S; - this.exportItem.Click += new EventHandler(this.exportItem_Click); + this.exportItemMD.Name = "exportItemMD"; + this.exportItemMD.Size = new Size(134, 22); + this.exportItemMD.Text = "Export MarkDown"; + this.exportItemMD.ShowShortcutKeys = true; + this.exportItemMD.Image = Properties.Resources.export; + this.exportItemMD.ShortcutKeys = Keys.Control | Keys.M; + this.exportItemMD.Click += new EventHandler(this.exportItemMD_Click); // // saveFile // diff --git a/Controls/TransparentLabel.cs b/Controls/TransparentLabel.cs new file mode 100644 index 000000000..e0070dfc4 --- /dev/null +++ b/Controls/TransparentLabel.cs @@ -0,0 +1,72 @@ +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; +using System.Windows.Forms; +namespace FallGuysStats { + public class TransparentLabel : Label { + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + cp.ExStyle |= 0x00000020; + return cp; + } + } + public TransparentLabel() { + DrawVisible = true; + TextRight = null; + Visible = false; + } + [DefaultValue(null)] + public string TextRight { get; set; } + [DefaultValue(true)] + public bool DrawVisible { get; set; } + public void Draw(Graphics g) { + if (!DrawVisible) { return; } + + using (SolidBrush brBack = new SolidBrush(BackColor)) { + using (SolidBrush brFore = new SolidBrush(ForeColor)) { + g.SmoothingMode = SmoothingMode.HighQuality; + g.InterpolationMode = InterpolationMode.HighQualityBilinear; + g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + + StringFormat stringFormat = new StringFormat(); + stringFormat.Alignment = StringAlignment.Near; + switch (TextAlign) { + case ContentAlignment.BottomLeft: + case ContentAlignment.BottomCenter: + case ContentAlignment.BottomRight: + stringFormat.LineAlignment = StringAlignment.Far; + break; + case ContentAlignment.MiddleLeft: + case ContentAlignment.MiddleCenter: + case ContentAlignment.MiddleRight: + stringFormat.LineAlignment = StringAlignment.Center; + break; + case ContentAlignment.TopLeft: + case ContentAlignment.TopCenter: + case ContentAlignment.TopRight: + stringFormat.LineAlignment = StringAlignment.Near; + break; + } + switch (TextAlign) { + case ContentAlignment.TopCenter: + case ContentAlignment.MiddleCenter: + case ContentAlignment.BottomCenter: + if (string.IsNullOrEmpty(TextRight)) { + stringFormat.Alignment = StringAlignment.Center; + } + break; + } + + g.DrawString(Text, Font, brFore, ClientRectangle, stringFormat); + + if (!string.IsNullOrEmpty(TextRight)) { + stringFormat.Alignment = StringAlignment.Far; + g.DrawString(TextRight, Font, brFore, ClientRectangle, stringFormat); + } + } + } + } + } +} \ No newline at end of file diff --git a/Entities/LevelStats.cs b/Entities/LevelStats.cs new file mode 100644 index 000000000..1d9e69c3f --- /dev/null +++ b/Entities/LevelStats.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using LiteDB; +namespace FallGuysStats { + public class RoundInfo { + public ObjectId ID { get; set; } + public int Profile { get; set; } + public string Name { get; set; } + public int ShowID { get; set; } + public int Round { get; set; } + public int Position { get; set; } + public int? Score { get; set; } + public int Tier { get; set; } + public bool Qualified { get; set; } + public int Kudos { get; set; } + public int Players { get; set; } + public bool InParty { get; set; } + public bool PrivateLobby { get; set; } + public DateTime Start { get; set; } = DateTime.MinValue; + public DateTime End { get; set; } = DateTime.MinValue; + public DateTime? Finish { get; set; } = null; + public bool Crown { get; set; } + public DateTime StartLocal; + public DateTime EndLocal; + public DateTime? FinishLocal; + public DateTime ShowStart = DateTime.MinValue; + public DateTime ShowEnd = DateTime.MinValue; + public int GameDuration; + public string SceneName; + public bool Playing; + private bool setLocalTime; + + public void ToLocalTime() { + if (setLocalTime) { return; } + setLocalTime = true; + + StartLocal = Start.ToLocalTime(); + EndLocal = End.ToLocalTime(); + if (Finish.HasValue) { + FinishLocal = Finish.Value.ToLocalTime(); + } + } + public void VerifyName() { + if (string.IsNullOrEmpty(SceneName)) { return; } + + string roundName; + if (LevelStats.SceneToRound.TryGetValue(SceneName, out roundName)) { + Name = roundName; + } + } + public string VerifiedName() { + if (string.IsNullOrEmpty(SceneName)) { return Name; } + + string roundName; + if (LevelStats.SceneToRound.TryGetValue(SceneName, out roundName)) { + return roundName; + } + return Name; + } + public override string ToString() { + return $"{Name}: Round={Round} Position={Position} Duration={End - Start} Kudos={Kudos}"; + } + public override bool Equals(object obj) { + return obj is RoundInfo info + && info.End == this.End + && info.Finish == this.Finish + && info.InParty == this.InParty + && info.Kudos == this.Kudos + && info.Players == this.Players + && info.Position == this.Position + && info.Qualified == this.Qualified + && info.Round == this.Round + && info.Score == this.Score + && info.ShowID == this.ShowID + && info.Start == this.Start + && info.Tier == this.Tier + && info.Name == this.Name; + } + public override int GetHashCode() { + return Name.GetHashCode() ^ ShowID ^ Round; + } + } + public enum QualifyTier { + None, + Gold, + Silver, + Bronze + } + public class LevelStats { + public static Dictionary ALL = new Dictionary(StringComparer.OrdinalIgnoreCase) { + { "round_biggestfan", new LevelStats("Big Fans", LevelType.Race, false, 2) }, + { "round_door_dash", new LevelStats("Door Dash", LevelType.Race, false, 1) }, + { "round_gauntlet_02", new LevelStats("Dizzy Heights", LevelType.Race, false, 1) }, + { "round_iceclimb", new LevelStats("Freezy Peak", LevelType.Race, false, 3) }, + { "round_dodge_fall", new LevelStats("Fruit Chute", LevelType.Race, false, 1) }, + { "round_chompchomp", new LevelStats("Gate Crash", LevelType.Race, false, 1) }, + { "round_gauntlet_01", new LevelStats("Hit Parade", LevelType.Race, false, 1) }, + { "round_hoops_blockade_solo", new LevelStats("Hoopsie Legends", LevelType.Hunt, false, 2) }, + { "round_gauntlet_04", new LevelStats("Knight Fever", LevelType.Race, false, 2) }, + { "round_see_saw", new LevelStats("See Saw", LevelType.Race, false, 1) }, + { "round_skeefall", new LevelStats("Ski Fall", LevelType.Hunt, false, 3) }, + { "round_lava", new LevelStats("Slime Climb", LevelType.Race, false, 1) }, + { "round_tip_toe", new LevelStats("Tip Toe", LevelType.Race, false, 1) }, + { "round_gauntlet_05", new LevelStats("Tundra Run", LevelType.Race, false, 3) }, + { "round_gauntlet_03", new LevelStats("Whirlygig", LevelType.Race, false, 1) }, + { "round_wall_guys", new LevelStats("Wall Guys", LevelType.Race, false, 2) }, + + { "round_block_party", new LevelStats("Block Party", LevelType.Survival, false, 1) }, + { "round_jump_club", new LevelStats("Jump Club", LevelType.Survival, false, 1) }, + { "round_match_fall", new LevelStats("Perfect Match", LevelType.Survival, false, 1) }, + { "round_tunnel", new LevelStats("Roll Out", LevelType.Survival, false, 1) }, + { "round_tail_tag", new LevelStats("Tail Tag", LevelType.Survival, false, 1) }, + + { "round_egg_grab", new LevelStats("Egg Scramble", LevelType.Team, false, 1) }, + { "round_egg_grab_02", new LevelStats("Egg Siege", LevelType.Team, false, 2) }, + { "round_fall_ball_60_players", new LevelStats("Fall Ball", LevelType.Team, false, 1) }, + { "round_ballhogs", new LevelStats("Hoarders", LevelType.Team, false, 1) }, + { "round_hoops", new LevelStats("Hoopsie Daisy", LevelType.Team, false, 1) }, + { "round_jinxed", new LevelStats("Jinxed", LevelType.Team, false, 1) }, + { "round_chicken_chase", new LevelStats("Pegwin Pursuit", LevelType.Team, false, 3) }, + { "round_rocknroll", new LevelStats("Rock'N'Roll", LevelType.Team, false, 1) }, + { "round_snowy_scrap", new LevelStats("Snowy Scrap", LevelType.Team, false, 3) }, + { "round_conveyor_arena", new LevelStats("Team Tail Tag", LevelType.Team, false, 1) }, + + { "round_fall_mountain_hub_complete", new LevelStats("Fall Mountain", LevelType.Race, true, 1) }, + { "round_floor_fall", new LevelStats("Hex-A-Gone", LevelType.Survival, true, 1) }, + { "round_jump_showdown", new LevelStats("Jump Showdown", LevelType.Survival, true, 1) }, + { "round_tunnel_final", new LevelStats("Roll Off", LevelType.Survival, true, 3) }, + { "round_royal_rumble", new LevelStats("Royal Fumble", LevelType.Hunt, true, 1) }, + { "round_thin_ice", new LevelStats("Thin Ice", LevelType.Survival, true, 3) } + }; + public static Dictionary SceneToRound = new Dictionary(StringComparer.OrdinalIgnoreCase) { + { "FallGuy_BiggestFan", "round_biggestfan" }, + { "FallGuy_DoorDash", "round_door_dash" }, + { "FallGuy_Gauntlet_02_01", "round_gauntlet_02" }, + { "FallGuy_IceClimb_01", "round_iceclimb" }, + { "FallGuy_DodgeFall", "round_dodge_fall" }, + { "FallGuy_ChompChomp_01", "round_chompchomp" }, + { "FallGuy_Gauntlet_01", "round_gauntlet_01" }, + { "FallGuy_Hoops_Blockade", "round_hoops_blockade_solo" }, + { "FallGuy_Gauntlet_04", "round_gauntlet_04" }, + { "FallGuy_SeeSaw_variant2", "round_see_saw" }, + { "FallGuy_SkeeFall", "round_skeefall" }, + { "FallGuy_Lava_02", "round_lava" }, + { "FallGuy_TipToe", "round_tip_toe" }, + { "FallGuy_Gauntlet_05", "round_gauntlet_05" }, + { "FallGuy_Gauntlet_03", "round_gauntlet_03" }, + { "FallGuy_WallGuys", "round_wall_guys" }, + + { "FallGuy_Block_Party", "round_block_party" }, + { "FallGuy_JumpClub_01", "round_jump_club" }, + { "FallGuy_MatchFall", "round_match_fall" }, + { "FallGuy_Tunnel_01", "round_tunnel" }, + { "FallGuy_TailTag_2", "round_tail_tag" }, + + { "FallGuy_EggGrab", "round_egg_grab" }, + { "FallGuy_EggGrab_02", "round_egg_grab_02" }, + { "FallGuy_FallBall_5", "round_fall_ball_60_players" }, + { "FallGuy_BallHogs_01", "round_ballhogs" }, + { "FallGuy_Hoops_01", "round_hoops" }, + { "FallGuy_TeamInfected", "round_jinxed" }, + { "FallGuy_ChickenChase_01", "round_chicken_chase" }, + { "FallGuy_RocknRoll", "round_rocknroll" }, + { "FallGuy_Snowy_Scrap", "round_snowy_scrap" }, + { "FallGuy_ConveyorArena_01", "round_conveyor_arena" }, + + { "FallGuy_FallMountain_Hub_Complete", "round_fall_mountain_hub_complete" }, + { "FallGuy_FloorFall", "round_floor_fall" }, + { "FallGuy_JumpShowdown_01", "round_jump_showdown" }, + { "FallGuy_Tunnel_Final", "round_tunnel_final" }, + { "FallGuy_Arena_01", "round_royal_rumble" }, + { "FallGuy_ThinIce", "round_thin_ice" } + }; + + public string Name { get; set; } + public int Qualified { get; set; } + public int Gold { get; set; } + public int Silver { get; set; } + public int Bronze { get; set; } + public int Played { get; set; } + public int Kudos { get; set; } + public TimeSpan Fastest { get; set; } + public TimeSpan Longest { get; set; } + public int AveKudos { get { return Kudos / (Played == 0 ? 1 : Played); } } + public LevelType Type; + public bool IsFinal; + public TimeSpan AveDuration { get { return TimeSpan.FromSeconds((int)Duration.TotalSeconds / (Played == 0 ? 1 : Played)); } } + public TimeSpan AveFinish { get { return TimeSpan.FromSeconds((double)FinishTime.TotalSeconds / (FinishedCount == 0 ? 1 : FinishedCount)); } } + public TimeSpan Duration; + public TimeSpan FinishTime; + public List Stats; + public int Season; + public int FinishedCount; + + public LevelStats(string levelName, LevelType type, bool isFinal, int season) { + Name = levelName; + Type = type; + Season = season; + IsFinal = isFinal; + Stats = new List(); + Clear(); + } + public void Clear() { + Qualified = 0; + Gold = 0; + Silver = 0; + Bronze = 0; + Played = 0; + Kudos = 0; + FinishedCount = 0; + Duration = TimeSpan.Zero; + FinishTime = TimeSpan.Zero; + Fastest = TimeSpan.Zero; + Longest = TimeSpan.Zero; + Stats.Clear(); + } + public void Add(RoundInfo stat) { + Stats.Add(stat); + if (!stat.PrivateLobby) { + Played++; + + switch (stat.Tier) { + case (int)QualifyTier.Gold: + Gold++; + break; + case (int)QualifyTier.Silver: + Silver++; + break; + case (int)QualifyTier.Bronze: + Bronze++; + break; + } + + Kudos += stat.Kudos; + Duration += stat.End - stat.Start; + Qualified += stat.Qualified ? 1 : 0; + } + + TimeSpan finishTime = stat.Finish.GetValueOrDefault(stat.End) - stat.Start; + if (stat.Finish.HasValue && finishTime.TotalSeconds > 1.1) { + if (!stat.PrivateLobby) { + FinishedCount++; + FinishTime += finishTime; + } + if (Fastest == TimeSpan.Zero || Fastest > finishTime) { + Fastest = finishTime; + } + if (Longest < finishTime) { + Longest = finishTime; + } + } + } + + public override string ToString() { + return $"{Name}: {Qualified} / {Played}"; + } + } +} \ No newline at end of file diff --git a/Entities/LevelType.cs b/Entities/LevelType.cs new file mode 100644 index 000000000..d03a1d2d8 --- /dev/null +++ b/Entities/LevelType.cs @@ -0,0 +1,23 @@ +namespace FallGuysStats { + public enum LevelType { + Race, + Survival, + Team, + Hunt, + Unknown + } + static class LevelTypeBehavior { + public static int FastestLabel(this LevelType type) { + switch (type) { + case LevelType.Race: + case LevelType.Hunt: + return 1; // FASTEST + case LevelType.Survival: + return 0; // LONGEST + case LevelType.Team: + return 2; // HIGH_SCORE + } + return 1; + } + } +} \ No newline at end of file diff --git a/Entities/LineReader.cs b/Entities/LineReader.cs new file mode 100644 index 000000000..214c186fe --- /dev/null +++ b/Entities/LineReader.cs @@ -0,0 +1,71 @@ +using System.IO; +using System.Text; +namespace FallGuysStats { + public class LineReader { + private byte[] buffer; + private int bufferIndex, bufferSize; + private Stream file; + private StringBuilder currentLine; + public long Position; + public LineReader(Stream stream) { + file = stream; + buffer = new byte[1024]; + currentLine = new StringBuilder(); + Position = stream.Position; + } + + public string ReadLine() { + while (bufferIndex < bufferSize) { + byte data = buffer[bufferIndex++]; + Position++; + + if (data == (byte)'\n' || data == (byte)'\r') { + if (data == '\r') { + data = bufferIndex < buffer.Length ? buffer[bufferIndex] : (byte)0; + if (data == (byte)'\n') { + bufferIndex++; + Position++; + } + } + + string result = currentLine.ToString(); + currentLine.Clear(); + return result; + } + + currentLine.Append((char)data); + } + + while ((bufferSize = file.Read(buffer, 0, buffer.Length)) > 0) { + bufferIndex = 0; + while (bufferIndex < bufferSize) { + byte data = buffer[bufferIndex++]; + Position++; + + if (data == (byte)'\n' || data == (byte)'\r') { + if (data == '\r') { + data = bufferIndex < buffer.Length ? buffer[bufferIndex] : (byte)0; + if (data == (byte)'\n') { + bufferIndex++; + Position++; + } + } + + string result = currentLine.ToString(); + currentLine.Clear(); + return result; + } + + currentLine.Append((char)data); + } + } + + if (currentLine.Length > 0) { + string result = currentLine.ToString(); + currentLine.Clear(); + return result; + } + return null; + } + } +} \ No newline at end of file diff --git a/Entities/LogFileWatcher.cs b/Entities/LogFileWatcher.cs new file mode 100644 index 000000000..f39ae4c7f --- /dev/null +++ b/Entities/LogFileWatcher.cs @@ -0,0 +1,386 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +namespace FallGuysStats { + public class LogLine { + public TimeSpan Time { get; } = TimeSpan.Zero; + public DateTime Date { get; set; } = DateTime.MinValue; + public string Line { get; set; } + public bool IsValid { get; set; } + public long Offset { get; set; } + + public LogLine(string line, long offset) { + Offset = offset; + Line = line; + IsValid = line.IndexOf(':') == 2 && line.IndexOf(':', 3) == 5 && line.IndexOf(':', 6) == 12; + if (IsValid) { + Time = TimeSpan.Parse(line.Substring(0, 12)); + } + } + + public override string ToString() { + return $"{Time}: {Line} ({Offset})"; + } + } + public class LogFileWatcher { + const int UpdateDelay = 500; + + private string filePath; + private string prevFilePath; + private List lines = new List(); + private bool running; + private bool stop; + private Thread watcher, parser; + + public event Action> OnParsedLogLines; + public event Action> OnParsedLogLinesCurrent; + public event Action OnNewLogFileDate; + public event Action OnError; + + public void Start(string logDirectory, string fileName) { + if (running) { return; } + + filePath = Path.Combine(logDirectory, fileName); + prevFilePath = Path.Combine(logDirectory, Path.GetFileNameWithoutExtension(fileName) + "-prev.log"); + stop = false; + watcher = new Thread(ReadLogFile) { IsBackground = true }; + watcher.Start(); + parser = new Thread(ParseLines) { IsBackground = true }; + parser.Start(); + } + + public async Task Stop() { + stop = true; + while (running || watcher == null || watcher.ThreadState == ThreadState.Unstarted) { + await Task.Delay(50); + } + lines = new List(); + await Task.Factory.StartNew(() => watcher?.Join()); + await Task.Factory.StartNew(() => parser?.Join()); + } + + private void ReadLogFile() { + running = true; + List tempLines = new List(); + DateTime lastDate = DateTime.MinValue; + bool completed = false; + string currentFilePath = prevFilePath; + long offset = 0; + while (!stop) { + try { + if (File.Exists(currentFilePath)) { + using (FileStream fs = new FileStream(currentFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { + tempLines.Clear(); + + if (fs.Length > offset) { + fs.Seek(offset, SeekOrigin.Begin); + + LineReader sr = new LineReader(fs); + string line; + DateTime currentDate = lastDate; + while ((line = sr.ReadLine()) != null) { + LogLine logLine = new LogLine(line, sr.Position); + + if (logLine.IsValid) { + int index; + if ((index = line.IndexOf("[GlobalGameStateClient].PreStart called at ")) > 0) { + currentDate = DateTime.SpecifyKind(DateTime.Parse(line.Substring(index + 43, 19)), DateTimeKind.Utc); + OnNewLogFileDate?.Invoke(currentDate); + } + + if (currentDate != DateTime.MinValue) { + if ((int)currentDate.TimeOfDay.TotalSeconds > (int)logLine.Time.TotalSeconds) { + currentDate = currentDate.AddDays(1); + } + currentDate = currentDate.AddSeconds(logLine.Time.TotalSeconds - currentDate.TimeOfDay.TotalSeconds); + logLine.Date = currentDate; + } + + if (line.IndexOf(" == [CompletedEpisodeDto] ==") > 0) { + StringBuilder sb = new StringBuilder(line); + sb.AppendLine(); + while ((line = sr.ReadLine()) != null) { + LogLine temp = new LogLine(line, fs.Position); + if (temp.IsValid) { + logLine.Line = sb.ToString(); + logLine.Offset = sr.Position; + tempLines.Add(logLine); + tempLines.Add(temp); + break; + } else if (!string.IsNullOrEmpty(line)) { + sb.AppendLine(line); + } + } + } else { + tempLines.Add(logLine); + } + } else if (logLine.Line.IndexOf("Client address: ", StringComparison.OrdinalIgnoreCase) > 0) { + tempLines.Add(logLine); + } + } + } else if (offset > fs.Length) { + offset = 0; + } + } + } + + if (tempLines.Count > 0) { + RoundInfo stat = null; + List round = new List(); + int players = 0; + bool countPlayers = false; + bool currentlyInParty = false; + bool privateLobby = false; + bool findPosition = false; + string currentPlayerID = string.Empty; + int lastPing = 0; + int gameDuration = 0; + for (int i = 0; i < tempLines.Count; i++) { + LogLine line = tempLines[i]; + if (ParseLine(line, round, ref currentPlayerID, ref countPlayers, ref currentlyInParty, ref findPosition, ref players, ref stat, ref lastPing, ref gameDuration, ref privateLobby)) { + lastDate = line.Date; + offset = line.Offset; + lock (lines) { + lines.AddRange(tempLines); + tempLines.Clear(); + } + break; + } else if (line.Line.IndexOf("[StateMatchmaking] Begin matchmaking", StringComparison.OrdinalIgnoreCase) > 0 || + line.Line.IndexOf("[GameStateMachine] Replacing FGClient.StateMainMenu with FGClient.StatePrivateLobby", StringComparison.OrdinalIgnoreCase) > 0) { + offset = i > 0 ? tempLines[i - 1].Offset : offset; + lastDate = line.Date; + } + } + + if (lastPing != 0) { + Stats.LastServerPing = lastPing; + } + OnParsedLogLinesCurrent?.Invoke(round); + } + + if (!completed) { + completed = true; + offset = 0; + currentFilePath = filePath; + } + } catch (Exception ex) { + OnError?.Invoke(ex.ToString()); + } + Thread.Sleep(UpdateDelay); + } + running = false; + } + private void ParseLines() { + RoundInfo stat = null; + List round = new List(); + List allStats = new List(); + int players = 0; + bool countPlayers = false; + bool currentlyInParty = false; + bool privateLobby = false; + bool findPosition = false; + string currentPlayerID = string.Empty; + int lastPing = 0; + int gameDuration = 0; + while (!stop) { + try { + lock (lines) { + for (int i = 0; i < lines.Count; i++) { + LogLine line = lines[i]; + if (ParseLine(line, round, ref currentPlayerID, ref countPlayers, ref currentlyInParty, ref findPosition, ref players, ref stat, ref lastPing, ref gameDuration, ref privateLobby)) { + allStats.AddRange(round); + } + } + + if (allStats.Count > 0) { + OnParsedLogLines?.Invoke(allStats); + allStats.Clear(); + } + + lines.Clear(); + } + } catch (Exception ex) { + OnError?.Invoke(ex.ToString()); + } + Thread.Sleep(UpdateDelay); + } + } + private bool ParseLine(LogLine line, List round, ref string currentPlayerID, ref bool countPlayers, ref bool currentlyInParty, ref bool findPosition, ref int players, ref RoundInfo stat, ref int lastPing, ref int gameDuration, ref bool privateLobby) { + int index; + if ((index = line.Line.IndexOf("[StateGameLoading] Loading game level scene", StringComparison.OrdinalIgnoreCase)) > 0) { + stat = new RoundInfo(); + int index2 = line.Line.IndexOf(' ', index + 44); + if (index2 < 0) { index2 = line.Line.Length; } + + stat.SceneName = line.Line.Substring(index + 44, index2 - index - 44); + findPosition = false; + round.Add(stat); + } else if (stat != null && (index = line.Line.IndexOf("[StateGameLoading] Finished loading game level", StringComparison.OrdinalIgnoreCase)) > 0) { + int index2 = line.Line.IndexOf(". ", index + 62); + if (index2 < 0) { index2 = line.Line.Length; } + + stat.Name = line.Line.Substring(index + 62, index2 - index - 62); + stat.Round = round.Count; + stat.Start = line.Date; + stat.InParty = currentlyInParty; + stat.PrivateLobby = privateLobby; + stat.GameDuration = gameDuration; + countPlayers = true; + } else if ((index = line.Line.IndexOf("[StateMatchmaking] Begin matchmaking", StringComparison.OrdinalIgnoreCase)) > 0 || + (index = line.Line.IndexOf("[GameStateMachine] Replacing FGClient.StateMainMenu with FGClient.StatePrivateLobby", StringComparison.OrdinalIgnoreCase)) > 0) { + privateLobby = line.Line.IndexOf("StatePrivateLobby") > 0; + currentlyInParty = privateLobby || !line.Line.Substring(index + 37).Equals("solo", StringComparison.OrdinalIgnoreCase); + if (stat != null) { + if (stat.End == DateTime.MinValue) { + stat.End = line.Date; + } + stat.Playing = false; + } + findPosition = false; + Stats.InShow = true; + round.Clear(); + stat = null; + } else if ((index = line.Line.IndexOf("NetworkGameOptions: durationInSeconds=", StringComparison.OrdinalIgnoreCase)) > 0) { + int nextIndex = line.Line.IndexOf(" ", index + 38); + gameDuration = int.Parse(line.Line.Substring(index + 38, nextIndex - index - 38)); + } else if (stat != null && countPlayers && line.Line.IndexOf("[ClientGameManager] Added player ", StringComparison.OrdinalIgnoreCase) > 0 && (index = line.Line.IndexOf(" players in system.", StringComparison.OrdinalIgnoreCase)) > 0) { + int prevIndex = line.Line.LastIndexOf(' ', index - 1); + if (int.TryParse(line.Line.Substring(prevIndex, index - prevIndex), out players)) { + stat.Players = players; + } + } else if ((index = line.Line.IndexOf("[ClientGameManager] Handling bootstrap for local player FallGuy [", StringComparison.OrdinalIgnoreCase)) > 0) { + int prevIndex = line.Line.IndexOf(']', index + 65); + currentPlayerID = line.Line.Substring(index + 65, prevIndex - index - 65); + } else if (stat != null && line.Line.IndexOf($"[ClientGameManager] Handling unspawn for player FallGuy [{currentPlayerID}]", StringComparison.OrdinalIgnoreCase) > 0) { + if (stat.End == DateTime.MinValue) { + stat.Finish = line.Date; + } else { + stat.Finish = stat.End; + } + findPosition = true; + } else if (stat != null && findPosition && (index = line.Line.IndexOf("[ClientGameSession] NumPlayersAchievingObjective=")) > 0) { + int position = int.Parse(line.Line.Substring(index + 49)); + if (position > 0) { + findPosition = false; + stat.Position = position; + } + } else if (stat != null && line.Line.IndexOf("Client address: ", StringComparison.OrdinalIgnoreCase) > 0) { + index = line.Line.IndexOf("RTT: "); + if (index > 0) { + int msIndex = line.Line.IndexOf("ms", index); + lastPing = int.Parse(line.Line.Substring(index + 5, msIndex - index - 5)); + } + } else if (stat != null && line.Line.IndexOf("[GameSession] Changing state from Countdown to Playing", StringComparison.OrdinalIgnoreCase) > 0) { + stat.Start = line.Date; + stat.Playing = true; + countPlayers = false; + } else if (stat != null && + (line.Line.IndexOf("[GameSession] Changing state from Playing to GameOver", StringComparison.OrdinalIgnoreCase) > 0 + || line.Line.IndexOf("Changing local player state to: SpectatingEliminated", StringComparison.OrdinalIgnoreCase) > 0 + || line.Line.IndexOf("[GlobalGameStateClient] SwitchToDisconnectingState", StringComparison.OrdinalIgnoreCase) > 0 + || line.Line.IndexOf("[GameStateMachine] Replacing FGClient.StatePrivateLobby with FGClient.StateMainMenu", StringComparison.OrdinalIgnoreCase) > 0)) { + if (stat.End == DateTime.MinValue) { + stat.End = line.Date; + } + stat.Playing = false; + } else if (line.Line.IndexOf("[StateMainMenu] Loading scene MainMenu", StringComparison.OrdinalIgnoreCase) > 0) { + if (stat != null) { + if (stat.End == DateTime.MinValue) { + stat.End = line.Date; + } + stat.Playing = false; + } + findPosition = false; + countPlayers = false; + Stats.InShow = false; + } else if (line.Line.IndexOf(" == [CompletedEpisodeDto] ==", StringComparison.OrdinalIgnoreCase) > 0) { + if (stat == null) { return false; } + + RoundInfo temp = null; + StringReader sr = new StringReader(line.Line); + string detail; + bool foundRound = false; + int maxRound = 0; + DateTime showStart = DateTime.MinValue; + while ((detail = sr.ReadLine()) != null) { + if (detail.IndexOf("[Round ", StringComparison.OrdinalIgnoreCase) == 0) { + foundRound = true; + int roundNum = (int)detail[7] - 0x30 + 1; + string roundName = detail.Substring(11, detail.Length - 12); + + if (roundNum - 1 < round.Count) { + if (roundNum > maxRound) { + maxRound = roundNum; + } + + temp = round[roundNum - 1]; + if (string.IsNullOrEmpty(temp.Name) || !temp.Name.Equals(roundName, StringComparison.OrdinalIgnoreCase)) { + return false; + } + + temp.VerifyName(); + if (roundNum == 1) { + showStart = temp.Start; + } + temp.ShowStart = showStart; + temp.Playing = false; + temp.Round = roundNum; + privateLobby = temp.PrivateLobby; + currentlyInParty = temp.InParty; + } else { + return false; + } + + if (temp.End == DateTime.MinValue) { + temp.End = line.Date; + } + if (temp.Start == DateTime.MinValue) { + temp.Start = temp.End; + } + if (!temp.Finish.HasValue) { + temp.Finish = temp.End; + } + } else if (foundRound) { + if (detail.IndexOf("> Position: ", StringComparison.OrdinalIgnoreCase) == 0) { + temp.Position = int.Parse(detail.Substring(12)); + } else if (detail.IndexOf("> Team Score: ", StringComparison.OrdinalIgnoreCase) == 0) { + temp.Score = int.Parse(detail.Substring(14)); + } else if (detail.IndexOf("> Qualified: ", StringComparison.OrdinalIgnoreCase) == 0) { + char qualified = detail[13]; + temp.Qualified = qualified == 'T'; + temp.Finish = temp.Qualified ? temp.Finish : null; + } else if (detail.IndexOf("> Bonus Tier: ", StringComparison.OrdinalIgnoreCase) == 0 && detail.Length == 15) { + char tier = detail[14]; + temp.Tier = (int)tier - 0x30 + 1; + } else if (detail.IndexOf("> Kudos: ", StringComparison.OrdinalIgnoreCase) == 0) { + temp.Kudos += int.Parse(detail.Substring(9)); + } else if (detail.IndexOf("> Bonus Kudos: ", StringComparison.OrdinalIgnoreCase) == 0) { + temp.Kudos += int.Parse(detail.Substring(15)); + } + } + } + + if (round.Count > maxRound) { + return false; + } + + stat = round[round.Count - 1]; + DateTime showEnd = stat.End; + for (int i = 0; i < round.Count; i++) { + round[i].ShowEnd = showEnd; + } + if (stat.Qualified) { + stat.Crown = true; + } + stat = null; + Stats.InShow = false; + Stats.EndedShow = true; + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Entities/StatSummary.cs b/Entities/StatSummary.cs new file mode 100644 index 000000000..580bcd3f4 --- /dev/null +++ b/Entities/StatSummary.cs @@ -0,0 +1,21 @@ +using System; +namespace FallGuysStats { + public class StatSummary { + public int CurrentStreak { get; set; } + public int CurrentFinalStreak { get; set; } + public int BestStreak { get; set; } + public int BestFinalStreak { get; set; } + public int AllWins { get; set; } + public int TotalWins { get; set; } + public int TotalShows { get; set; } + public int TotalFinals { get; set; } + public int TotalPlays { get; set; } + public int TotalQualify { get; set; } + public int TotalGolds { get; set; } + public TimeSpan? BestFinish { get; set; } + public TimeSpan? BestFinishOverall { get; set; } + public TimeSpan? LongestFinish { get; set; } + public TimeSpan? LongestFinishOverall { get; set; } + public int? BestScore { get; set; } + } +} \ No newline at end of file diff --git a/Entities/UserSettings.cs b/Entities/UserSettings.cs new file mode 100644 index 000000000..4b6ec829b --- /dev/null +++ b/Entities/UserSettings.cs @@ -0,0 +1,49 @@ +namespace FallGuysStats { + public class UserSettings { + public int ID { get; set; } + public string LogPath { get; set; } + public int FilterType { get; set; } + public int SelectedProfile { get; set; } + public int? OverlayLocationX { get; set; } + public int? OverlayLocationY { get; set; } + public int OverlayColor { get; set; } + public bool FlippedDisplay { get; set; } + public bool SwitchBetweenLongest { get; set; } + public bool SwitchBetweenQualify { get; set; } + public bool SwitchBetweenPlayers { get; set; } + public bool SwitchBetweenStreaks { get; set; } + public bool OnlyShowLongest { get; set; } + public bool OnlyShowGold { get; set; } + public bool OnlyShowPing { get; set; } + public bool OnlyShowFinalStreak { get; set; } + public int CycleTimeSeconds { get; set; } + public bool OverlayVisible { get; set; } + public bool OverlayNotOnTop { get; set; } + public int PreviousWins { get; set; } + public bool UseNDI { get; set; } + public int WinsFilter { get; set; } + public int FastestFilter { get; set; } + public int QualifyFilter { get; set; } + public bool HideWinsInfo { get; set; } + public bool HideRoundInfo { get; set; } + public bool HideTimeInfo { get; set; } + public bool ShowOverlayTabs { get; set; } + public bool ShowPercentages { get; set; } + public bool UpdatedDateFormat { get; set; } + public bool AutoUpdate { get; set; } + public int? FormLocationX { get; set; } + public int? FormLocationY { get; set; } + public int? FormWidth { get; set; } + public int? FormHeight { get; set; } + public int? OverlayWidth { get; set; } + public int? OverlayHeight { get; set; } + public double OverlayScale { get; set; } + public bool HideOverlayPercentages { get; set; } + public bool HoopsieHeros { get; set; } + public int Version { get; set; } + public bool IgnoreLevelTypeWhenSorting { get; set; } + public string GameExeLocation { get; set; } + public bool AutoLaunchGameOnStartup { get; set; } + public string OverlayFontSerialized { get; set; } + } +} \ No newline at end of file diff --git a/Entities/ZipWebClient.cs b/Entities/ZipWebClient.cs new file mode 100644 index 000000000..fceaa63f0 --- /dev/null +++ b/Entities/ZipWebClient.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; +using System.Net.Cache; +using System.Text; +namespace FallGuysStats { + public class ZipWebClient : WebClient { + public ZipWebClient() : base() { + this.Encoding = Encoding.GetEncoding(1252); + this.Headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0)"; + this.Headers["Accept-Encoding"] = "gzip, deflate"; + this.Headers["Accept-Language"] = "en-us"; + this.Headers["Accept"] = "text/html, application/xhtml+xml, */*"; + } + protected override WebRequest GetWebRequest(Uri address) { + HttpRequestCachePolicy requestPolicy = new HttpRequestCachePolicy(HttpCacheAgeControl.MaxAge, TimeSpan.FromSeconds(10)); + + HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address); + request.CachePolicy = requestPolicy; + request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + return request; + } + } +} \ No newline at end of file diff --git a/FallGuyStats.zip b/FallGuyStats.zip new file mode 100644 index 000000000..6dfe5b807 Binary files /dev/null and b/FallGuyStats.zip differ diff --git a/FallGuyStatsManualUpdate.zip b/FallGuyStatsManualUpdate.zip new file mode 100644 index 000000000..746793a8d Binary files /dev/null and b/FallGuyStatsManualUpdate.zip differ diff --git a/FallGuysStats.csproj b/FallGuysStats.csproj index 29df33771..4f29c8e49 100644 --- a/FallGuysStats.csproj +++ b/FallGuysStats.csproj @@ -22,6 +22,8 @@ DEBUG;TRACE prompt 4 + false + false AnyCPU @@ -31,9 +33,20 @@ TRACE prompt 4 + false + false - fall.ico + Resources\fall.ico + + + bin\ + TRACE;AllowUpdate + true + pdbonly + AnyCPU + 7.3 + prompt @@ -44,34 +57,83 @@ + + + - + Component - + + Component + + + Component + + + + + + + + + + + + + + Form - + LevelDetails.cs - + + Form + + + Overlay.cs + + Form - + + Settings.cs + + + Form + + Stats.cs - + - - + + + Component + + + Form + + + StatsDisplay.cs + + LevelDetails.cs - + + Overlay.cs + + + Settings.cs + + Stats.cs + Designer ResXFileCodeGenerator @@ -83,6 +145,9 @@ Resources.resx True + + StatsDisplay.cs + SettingsSingleFileGenerator Settings.Designer.cs @@ -92,15 +157,19 @@ Settings.settings True + - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/FallGuysStats.sln b/FallGuysStats.sln index 69c9b018d..00422f607 100644 --- a/FallGuysStats.sln +++ b/FallGuysStats.sln @@ -5,16 +5,24 @@ VisualStudioVersion = 16.0.30406.217 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FallGuysStats", "FallGuysStats.csproj", "{9DDF7BD8-3C77-43D4-A229-F79CE082C6E8}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{48C46DB6-1CE3-4B93-B980-6FDEC903A42B}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + ReleaseUpdate|Any CPU = ReleaseUpdate|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9DDF7BD8-3C77-43D4-A229-F79CE082C6E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9DDF7BD8-3C77-43D4-A229-F79CE082C6E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {9DDF7BD8-3C77-43D4-A229-F79CE082C6E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {9DDF7BD8-3C77-43D4-A229-F79CE082C6E8}.Release|Any CPU.Build.0 = Release|Any CPU + {9DDF7BD8-3C77-43D4-A229-F79CE082C6E8}.ReleaseUpdate|Any CPU.ActiveCfg = ReleaseUpdate|Any CPU + {9DDF7BD8-3C77-43D4-A229-F79CE082C6E8}.ReleaseUpdate|Any CPU.Build.0 = ReleaseUpdate|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LevelDetails.cs b/LevelDetails.cs deleted file mode 100644 index 6ab83185e..000000000 --- a/LevelDetails.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections.Generic; -using System.Windows.Forms; -namespace FallGuysStats { - public partial class LevelDetails : Form { - public string LevelName { get; set; } - public List RoundDetails { get; set; } - public LevelDetails() { - InitializeComponent(); - } - - private void LevelDetails_Load(object sender, System.EventArgs e) { - gridDetails.DataSource = RoundDetails; - Text = $"Level Stats - {LevelName}"; - } - private void gridDetails_DataSourceChanged(object sender, System.EventArgs e) { - if (gridDetails.Columns.Count == 0) { return; } - int pos = 0; - gridDetails.Columns["Name"].Visible = false; - gridDetails.Columns["Tier"].Visible = false; - gridDetails.Columns.Add(new DataGridViewImageColumn() { Name = "Medal", ImageLayout = DataGridViewImageCellLayout.Zoom, ToolTipText = "Medal" }); - gridDetails.Setup("Medal", pos++, 24, "", DataGridViewContentAlignment.MiddleCenter); - gridDetails.Setup("ShowID", pos++, 0, "Show", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Round", pos++, 50, "Round", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Players", pos++, 60, "Players", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Start", pos++, 115, "Start", DataGridViewContentAlignment.MiddleCenter); - gridDetails.Setup("End", pos++, 60, "Duration", DataGridViewContentAlignment.MiddleCenter); - gridDetails.Setup("Qualified", pos++, 70, "Qualified", DataGridViewContentAlignment.MiddleCenter); - gridDetails.Setup("Position", pos++, 60, "Position", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Kudos", pos++, 60, "Kudos", DataGridViewContentAlignment.MiddleRight); - } - private void gridDetails_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { - if (gridDetails.Columns[e.ColumnIndex].Name == "End") { - RoundInfo info = gridDetails.Rows[e.RowIndex].DataBoundItem as RoundInfo; - e.Value = (info.End - info.Start).ToString("m\\:ss"); - } else if (gridDetails.Columns[e.ColumnIndex].Name == "Medal" && e.Value == null) { - RoundInfo info = gridDetails.Rows[e.RowIndex].DataBoundItem as RoundInfo; - if (info.Qualified) { - switch (info.Tier) { - case 0: e.Value = Properties.Resources.medal_pink; break; - case 1: e.Value = Properties.Resources.medal_gold; break; - case 2: e.Value = Properties.Resources.medal_silver; break; - case 3: e.Value = Properties.Resources.medal_bronze; break; - } - } else { - e.Value = Properties.Resources.medal_eliminated; - } - } - } - private void gridDetails_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) { - SortOrder sortOrder = GetSortOrder(e.ColumnIndex); - RoundDetails.Sort(delegate (RoundInfo one, RoundInfo two) { - if (sortOrder == SortOrder.Descending) { - RoundInfo temp = one; - one = two; - two = temp; - } - switch (gridDetails.Columns[e.ColumnIndex].Name) { - case "ShowID": return one.ShowID.CompareTo(two.ShowID); - case "Round": return one.Round.CompareTo(two.Round); - case "Players": return one.Players.CompareTo(two.Players); - case "Start": return one.Start.CompareTo(two.Start); - case "End": return (one.End - one.Start).CompareTo(two.End - two.Start); - case "Qualified": return one.Qualified.CompareTo(two.Qualified); - case "Position": return one.Position.CompareTo(two.Position); - case "Medal": - int tierOne = one.Qualified ? one.Tier == 0 ? 4 : one.Tier : 5; - int tierTwo = two.Qualified ? two.Tier == 0 ? 4 : two.Tier : 5; - return tierOne.CompareTo(tierTwo); - default: return one.Kudos.CompareTo(two.Kudos); - } - }); - gridDetails.DataSource = null; - gridDetails.DataSource = RoundDetails; - gridDetails.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = sortOrder; - } - private SortOrder GetSortOrder(int columnIndex) { - if (gridDetails.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.None || - gridDetails.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.Descending) { - gridDetails.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending; - return SortOrder.Ascending; - } else { - gridDetails.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Descending; - return SortOrder.Descending; - } - } - } -} \ No newline at end of file diff --git a/LevelStats.cs b/LevelStats.cs deleted file mode 100644 index 5ca2b49bc..000000000 --- a/LevelStats.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -namespace FallGuysStats { - public class RoundInfo { - public string Name { get; set; } - public int ShowID { get; set; } - public int Round { get; set; } - public int Position { get; set; } - public int Tier { get; set; } - public bool Qualified { get; set; } - public int Kudos { get; set; } - public int Players { get; set; } - public DateTime Start { get; set; } = DateTime.MinValue; - public DateTime End { get; set; } = DateTime.MinValue; - - public void ToLocalTime() { - Start = Start.Add(Start - Start.ToUniversalTime()); - End = End.Add(End - End.ToUniversalTime()); - } - public override string ToString() { - return $"{Name}: Round={Round} Position={Position} Duration={End - Start} Kudos={Kudos}"; - } - } - public class LevelStats { - public string Name { get; set; } - public int Qualified { get; set; } - public int Gold { get; set; } - public int Silver { get; set; } - public int Bronze { get; set; } - public int Played { get; set; } - public int Kudos { get; set; } - public int AveKudos { get { return Kudos / (Played == 0 ? 1 : Played); } } - public TimeSpan AveDuration { get { return TimeSpan.FromSeconds((int)Duration.TotalSeconds / (Played == 0 ? 1 : Played)); } } - public TimeSpan Duration; - public string LevelName; - public List Stats; - - public LevelStats(string name, string levelName) { - Name = name; - LevelName = levelName; - Stats = new List(); - Clear(); - } - public void Clear() { - Qualified = 0; - Gold = 0; - Silver = 0; - Bronze = 0; - Played = 0; - Kudos = 0; - Duration = TimeSpan.Zero; - Stats.Clear(); - } - public void Add(RoundInfo stat) { - Stats.Add(stat); - Played++; - if (stat.Tier == 1) { - Gold++; - } else if (stat.Tier == 2) { - Silver++; - } else if (stat.Tier == 3) { - Bronze++; - } - Kudos += stat.Kudos; - Duration += stat.End - stat.Start; - Qualified += stat.Qualified ? 1 : 0; - } - - public override string ToString() { - return $"{Name}: {Qualified} / {Played}"; - } - } -} \ No newline at end of file diff --git a/LogFileWatcher.cs b/LogFileWatcher.cs deleted file mode 100644 index 6e717e29f..000000000 --- a/LogFileWatcher.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -namespace FallGuysStats { - public class LogLine { - public string Namespace { get; set; } - public TimeSpan Time { get; } = TimeSpan.Zero; - public DateTime Date { get; set; } = DateTime.MinValue; - public string Line { get; set; } - - public LogLine(string ns, string line) { - Namespace = ns; - Line = line; - if (line.IndexOf(':') == 2 && line.IndexOf(':', 3) == 5 && line.IndexOf(':', 6) == 12) { - Time = TimeSpan.Parse(line.Substring(0, 12)); - } - } - - public override string ToString() { - return $"{Time}: {Line}"; - } - } - public class LogFileWatcher { - const int UpdateDelay = 500; - - private string filePath; - private string fileName; - private List lines = new List(); - private bool logFileExists; - private long offset; - private bool running; - private bool stop; - private Thread watcher, parser; - - public event Action> OnParsedLogLines; - public event Action OnLogFileFound; - - public void Start(string logDirectory, string fileName) { - if (running) { return; } - - this.fileName = fileName; - filePath = Path.Combine(logDirectory, fileName); - stop = false; - offset = 0; - logFileExists = false; - watcher = new Thread(ReadLogFile) { IsBackground = true }; - watcher.Start(); - parser = new Thread(ParseLines) { IsBackground = true }; - parser.Start(); - } - - public async Task Stop() { - stop = true; - while (running || watcher == null || watcher.ThreadState == ThreadState.Unstarted) { - await Task.Delay(50); - } - lines = new List(); - await Task.Factory.StartNew(() => watcher?.Join()); - } - - private void ReadLogFile() { - running = true; - List currentLines = new List(); - List tempLines = new List(); - DateTime lastDate = DateTime.MinValue; - while (!stop) { - FileInfo fileInfo = new FileInfo(filePath); - if (fileInfo.Exists) { - if (!logFileExists) { - logFileExists = true; - OnLogFileFound?.Invoke(fileName); - } - - using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - tempLines.Clear(); - fs.Seek(offset, SeekOrigin.Begin); - - if (fs.Length > offset) { - using (StreamReader sr = new StreamReader(fs)) { - string line; - DateTime currentDate = lastDate; - while (!sr.EndOfStream && (line = sr.ReadLine()) != null) { - LogLine logLine = new LogLine(fileName, line); - - if (logLine.Time != TimeSpan.Zero) { - int index; - if ((index = line.IndexOf("[GlobalGameStateClient].PreStart called at ")) > 0) { - currentDate = DateTime.Parse(line.Substring(index + 43, 19)); - } - - if (currentDate != DateTime.MinValue) { - if (currentDate.TimeOfDay > logLine.Time) { - currentDate = currentDate.AddDays(1); - } - currentDate = currentDate.AddSeconds(logLine.Time.TotalSeconds - currentDate.TimeOfDay.TotalSeconds); - logLine.Date = currentDate; - } - - if (line.IndexOf(" == [CompletedEpisodeDto] ==") > 0) { - StringBuilder sb = new StringBuilder(line); - sb.AppendLine(); - while (!sr.EndOfStream && (line = sr.ReadLine()) != null) { - LogLine temp = new LogLine(fileName, line); - if (temp.Time != TimeSpan.Zero) { - logLine.Line = sb.ToString(); - currentLines.AddRange(tempLines); - currentLines.Add(logLine); - currentLines.Add(temp); - lastDate = currentDate; - offset = fs.Position; - tempLines.Clear(); - break; - } else if (!string.IsNullOrEmpty(line)) { - sb.AppendLine(line); - } - } - } else { - tempLines.Add(logLine); - } - } - } - } - } else if (offset > fs.Length) { - offset = 0; - } - } - } - - if (currentLines.Count > 0) { - lock (lines) { - lines.AddRange(currentLines); - currentLines.Clear(); - } - } - Thread.Sleep(UpdateDelay); - } - running = false; - } - private void ParseLines() { - RoundInfo stat = null; - List round = new List(); - List allStats = new List(); - int players; - bool countPlayers = false; - while (!stop) { - lock (lines) { - for (int i = 0; i < lines.Count; i++) { - LogLine line = lines[i]; - int index; - if ((index = line.Line.IndexOf("[StateGameLoading] Finished loading game level")) > 0) { - stat = new RoundInfo(); - round.Add(stat); - stat.Name = line.Line.Substring(index + 62); - countPlayers = true; - } else if (stat != null && countPlayers && line.Line.IndexOf("[ClientGameManager] Added player ") > 0 && (index = line.Line.IndexOf(" players in system.")) > 0) { - int prevIndex = line.Line.LastIndexOf(' ', index - 1); - if (int.TryParse(line.Line.Substring(prevIndex, index - prevIndex), out players)) { - stat.Players = players; - } - } else if (stat != null && line.Line.IndexOf("[GameSession] Changing state from Countdown to Playing") > 0) { - stat.Start = line.Date; - countPlayers = false; - } else if (stat != null && - (line.Line.IndexOf("[GameSession] Changing state from Playing to GameOver") > 0 - || line.Line.IndexOf("Changing local player state to: SpectatingEliminated") > 0)) { - stat.End = line.Date; - } else if (line.Line.IndexOf("[StateMainMenu] Loading scene MainMenu") > 0) { - round.Clear(); - stat = null; - } else if (line.Line.IndexOf(" == [CompletedEpisodeDto] ==") > 0) { - if (stat.End == DateTime.MinValue) { - stat.End = line.Date; - } - - StringReader sr = new StringReader(line.Line); - string detail; - bool foundRound = false; - while ((detail = sr.ReadLine()) != null) { - if (detail.IndexOf("[Round ") == 0) { - foundRound = true; - int roundNum = (int)detail[7] - 0x30 + 1; - stat = round[roundNum - 1]; - stat.Round = roundNum; - } else if (foundRound) { - if (detail.IndexOf("> Position: ") == 0) { - switch (stat.Name) { - case "round_block_party": - case "round_jump_club": - case "round_match_fall": - case "round_tunnel": - case "round_tail_tag": - case "round_fall_ball_60_players": - case "round_jinxed": - case "round_egg_grab": - case "round_ballhogs": - case "round_hoops": - case "round_rocknroll": - case "round_conveyor_arena": - break; - default: - stat.Position = int.Parse(detail.Substring(12)); - break; - } - } else if (detail.IndexOf("> Qualified: ") == 0) { - char qualified = detail[13]; - stat.Qualified = qualified == 'T'; - } else if (detail.IndexOf("> Bonus Tier: ") == 0 && detail.Length == 15) { - char tier = detail[14]; - stat.Tier = (int)tier - 0x30 + 1; - } else if (detail.IndexOf("> Kudos: ") == 0) { - stat.Kudos += int.Parse(detail.Substring(9)); - } else if (detail.IndexOf("> Bonus Kudos: ") == 0) { - stat.Kudos += int.Parse(detail.Substring(15)); - } - } - } - - allStats.AddRange(round); - round.Clear(); - stat = null; - } - } - - if (allStats.Count > 0) { - OnParsedLogLines?.Invoke(allStats); - allStats.Clear(); - } - - lines.Clear(); - } - Thread.Sleep(UpdateDelay); - } - } - } -} \ No newline at end of file diff --git a/NDI.zip b/NDI.zip new file mode 100644 index 000000000..f38f29bc4 Binary files /dev/null and b/NDI.zip differ diff --git a/NDI/AudioFrame.cs b/NDI/AudioFrame.cs new file mode 100644 index 000000000..b9e9736af --- /dev/null +++ b/NDI/AudioFrame.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; +using System.Xml.Linq; +namespace NewTek.NDI { +#if AllowUpdate + public class AudioFrame : IDisposable { + public AudioFrame(int maxSamples, int sampleRate, int numChannels) { + // we have to know to free it later + _memoryOwned = true; + + IntPtr audioBufferPtr = Marshal.AllocHGlobal(numChannels * maxSamples * sizeof(float)); + + _ndiAudioFrame = new NDIlib.audio_frame_v2_t() { + sample_rate = sampleRate, + no_channels = numChannels, + no_samples = maxSamples, + timecode = NDIlib.send_timecode_synthesize, + p_data = audioBufferPtr, + channel_stride_in_bytes = sizeof(float) * maxSamples, + p_metadata = IntPtr.Zero, + timestamp = 0 + }; + } + + public AudioFrame(IntPtr bufferPtr, int sampleRate, int numChannels, int channelStride, int numSamples) { + _ndiAudioFrame = new NDIlib.audio_frame_v2_t() { + sample_rate = 48000, + no_channels = 2, + no_samples = 1602, + timecode = NDIlib.send_timecode_synthesize, + p_data = bufferPtr, + channel_stride_in_bytes = channelStride, + p_metadata = IntPtr.Zero, + timestamp = 0 + }; + } + + public IntPtr AudioBuffer { + get { + return _ndiAudioFrame.p_data; + } + } + + public int NumSamples { + get { + return _ndiAudioFrame.no_samples; + } + + set { + _ndiAudioFrame.no_samples = value; + } + } + + public int NumChannels { + get { + return _ndiAudioFrame.no_channels; + } + + set { + _ndiAudioFrame.no_channels = value; + } + } + + public int ChannelStride { + get { + return _ndiAudioFrame.channel_stride_in_bytes; + } + + set { + _ndiAudioFrame.channel_stride_in_bytes = value; + } + } + + public int SampleRate { + get { + return _ndiAudioFrame.sample_rate; + } + + set { + _ndiAudioFrame.sample_rate = value; + } + } + + public Int64 TimeStamp { + get { + return _ndiAudioFrame.timestamp; + } + set { + _ndiAudioFrame.timestamp = value; + } + } + + public XElement MetaData { + get { + if (_ndiAudioFrame.p_metadata == IntPtr.Zero) + return null; + + String mdString = UTF.Utf8ToString(_ndiAudioFrame.p_metadata); + if (String.IsNullOrEmpty(mdString)) + return null; + + return XElement.Parse(mdString); + } + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~AudioFrame() { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (_memoryOwned && _ndiAudioFrame.p_data != IntPtr.Zero) { + Marshal.FreeHGlobal(_ndiAudioFrame.p_data); + _ndiAudioFrame.p_data = IntPtr.Zero; + } + + NDIlib.destroy(); + } + } + + internal NDIlib.audio_frame_v2_t _ndiAudioFrame; + bool _memoryOwned = false; + } +#endif +} \ No newline at end of file diff --git a/NDI/Pinvoke/Processing.NDI.Lib.Interop.cs b/NDI/Pinvoke/Processing.NDI.Lib.Interop.cs new file mode 100644 index 000000000..90d5071bd --- /dev/null +++ b/NDI/Pinvoke/Processing.NDI.Lib.Interop.cs @@ -0,0 +1,78 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +namespace NewTek { +#if AllowUpdate + [SuppressUnmanagedCodeSecurity] + public static partial class NDIlib { + public static UInt32 NDILIB_CPP_DEFAULT_CONSTRUCTORS = 0; + + // This is not actually required, but will start and end the libraries which might get + // you slightly better performance in some cases. In general it is more "correct" to + // call these although it is not required. There is no way to call these that would have + // an adverse impact on anything (even calling destroy before you've deleted all your + // objects). This will return false if the CPU is not sufficiently capable to run NDILib + // currently NDILib requires SSE4.2 instructions (see documentation). You can verify + // a specific CPU against the library with a call to NDIlib_is_supported_CPU() + public static bool initialize() { + if (IntPtr.Size == 8) + return UnsafeNativeMethods.initialize_64(); + else + return UnsafeNativeMethods.initialize_32(); + } + + public static void destroy() { + if (IntPtr.Size == 8) + UnsafeNativeMethods.destroy_64(); + else + UnsafeNativeMethods.destroy_32(); + } + + public static IntPtr version() { + if (IntPtr.Size == 8) + return UnsafeNativeMethods.version_64(); + else + return UnsafeNativeMethods.version_32(); + } + + // Recover whether the current CPU in the system is capable of running NDILib. + public static bool is_supported_CPU() { + if (IntPtr.Size == 8) + return UnsafeNativeMethods.is_supported_CPU_64(); + else + return UnsafeNativeMethods.is_supported_CPU_32(); + } + + [SuppressUnmanagedCodeSecurity] + internal static partial class UnsafeNativeMethods { + // initialize + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_initialize", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAsAttribute(UnmanagedType.U1)] + internal static extern bool initialize_64(); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_initialize", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAsAttribute(UnmanagedType.U1)] + internal static extern bool initialize_32(); + + // destroy + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_destroy", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void destroy_64(); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_destroy", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void destroy_32(); + + // version + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_version", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr version_64(); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_version", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr version_32(); + + // is_supported_CPU + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_is_supported_CPU", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAsAttribute(UnmanagedType.U1)] + internal static extern bool is_supported_CPU_64(); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_is_supported_CPU", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAsAttribute(UnmanagedType.U1)] + internal static extern bool is_supported_CPU_32(); + } // UnsafeNativeMethods + } // class NDIlib +#endif +} // namespace NewTek \ No newline at end of file diff --git a/NDI/Pinvoke/Processing.NDI.Send.Interop.cs b/NDI/Pinvoke/Processing.NDI.Send.Interop.cs new file mode 100644 index 000000000..a189eb6d4 --- /dev/null +++ b/NDI/Pinvoke/Processing.NDI.Send.Interop.cs @@ -0,0 +1,237 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +namespace NewTek { +#if AllowUpdate + [SuppressUnmanagedCodeSecurity] + public static partial class NDIlib { + // The creation structure that is used when you are creating a sender + [StructLayoutAttribute(LayoutKind.Sequential)] + public struct send_create_t { + // The name of the NDI source to create. This is a NULL terminated UTF8 string. + public IntPtr p_ndi_name; + + // What groups should this source be part of. NULL means default. + public IntPtr p_groups; + + // Do you want audio and video to "clock" themselves. When they are clocked then + // by adding video frames, they will be rate limited to match the current frame-rate + // that you are submitting at. The same is true for audio. In general if you are submitting + // video and audio off a single thread then you should only clock one of them (video is + // probably the better of the two to clock off). If you are submtiting audio and video + // of separate threads then having both clocked can be useful. + [MarshalAsAttribute(UnmanagedType.U1)] + public bool clock_video, clock_audio; + } + + // Create a new sender instance. This will return NULL if it fails. + public static IntPtr send_create(ref send_create_t p_create_settings) { + if (IntPtr.Size == 8) + return UnsafeNativeMethods.send_create_64(ref p_create_settings); + else + return UnsafeNativeMethods.send_create_32(ref p_create_settings); + } + + // This will destroy an existing finder instance. + public static void send_destroy(IntPtr p_instance) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_destroy_64(p_instance); + else + UnsafeNativeMethods.send_destroy_32(p_instance); + } + + // This will add a video frame + public static void send_send_video_v2(IntPtr p_instance, ref video_frame_v2_t p_video_data) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_send_video_v2_64(p_instance, ref p_video_data); + else + UnsafeNativeMethods.send_send_video_v2_32(p_instance, ref p_video_data); + } + + // This will add a video frame and will return immediately, having scheduled the frame to be displayed. + // All processing and sending of the video will occur asynchronously. The memory accessed by NDIlib_video_frame_t + // cannot be freed or re-used by the caller until a synchronizing event has occurred. In general the API is better + // able to take advantage of asynchronous processing than you might be able to by simple having a separate thread + // to submit frames. + // + // This call is particularly beneficial when processing BGRA video since it allows any color conversion, compression + // and network sending to all be done on separate threads from your main rendering thread. + // + // Synchronozing events are : + // - a call to NDIlib_send_send_video + // - a call to NDIlib_send_send_video_async with another frame to be sent + // - a call to NDIlib_send_send_video with p_video_data=NULL + // - a call to NDIlib_send_destroy + public static void send_send_video_async_v2(IntPtr p_instance, ref video_frame_v2_t p_video_data) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_send_video_async_v2_64(p_instance, ref p_video_data); + else + UnsafeNativeMethods.send_send_video_async_v2_32(p_instance, ref p_video_data); + } + + // This will add an audio frame + public static void send_send_audio_v2(IntPtr p_instance, ref audio_frame_v2_t p_audio_data) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_send_audio_v2_64(p_instance, ref p_audio_data); + else + UnsafeNativeMethods.send_send_audio_v2_32(p_instance, ref p_audio_data); + } + + // This will add a metadata frame + public static void send_send_metadata(IntPtr p_instance, ref metadata_frame_t p_metadata) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_send_metadata_64(p_instance, ref p_metadata); + else + UnsafeNativeMethods.send_send_metadata_32(p_instance, ref p_metadata); + } + + // This allows you to receive metadata from the other end of the connection + public static frame_type_e send_capture(IntPtr p_instance, ref metadata_frame_t p_metadata, UInt32 timeout_in_ms) { + if (IntPtr.Size == 8) + return UnsafeNativeMethods.send_capture_64(p_instance, ref p_metadata, timeout_in_ms); + else + return UnsafeNativeMethods.send_capture_32(p_instance, ref p_metadata, timeout_in_ms); + } + + // Free the buffers returned by capture for metadata + public static void send_free_metadata(IntPtr p_instance, ref metadata_frame_t p_metadata) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_free_metadata_64(p_instance, ref p_metadata); + else + UnsafeNativeMethods.send_free_metadata_32(p_instance, ref p_metadata); + } + + // Determine the current tally sate. If you specify a timeout then it will wait until it has changed, otherwise it will simply poll it + // and return the current tally immediately. The return value is whether anything has actually change (true) or whether it timed out (false) + public static bool send_get_tally(IntPtr p_instance, ref tally_t p_tally, UInt32 timeout_in_ms) { + if (IntPtr.Size == 8) + return UnsafeNativeMethods.send_get_tally_64(p_instance, ref p_tally, timeout_in_ms); + else + return UnsafeNativeMethods.send_get_tally_32(p_instance, ref p_tally, timeout_in_ms); + } + + // Get the current number of receivers connected to this source. This can be used to avoid even rendering when nothing is connected to the video source. + // which can significantly improve the efficiency if you want to make a lot of sources available on the network. If you specify a timeout that is not + // 0 then it will wait until there are connections for this amount of time. + public static int send_get_no_connections(IntPtr p_instance, UInt32 timeout_in_ms) { + if (IntPtr.Size == 8) + return UnsafeNativeMethods.send_get_no_connections_64(p_instance, timeout_in_ms); + else + return UnsafeNativeMethods.send_get_no_connections_32(p_instance, timeout_in_ms); + } + + // Connection based metadata is data that is sent automatically each time a new connection is received. You queue all of these + // up and they are sent on each connection. To reset them you need to clear them all and set them up again. + public static void send_clear_connection_metadata(IntPtr p_instance) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_clear_connection_metadata_64(p_instance); + else + UnsafeNativeMethods.send_clear_connection_metadata_32(p_instance); + } + + // Add a connection metadata string to the list of what is sent on each new connection. If someone is already connected then + // this string will be sent to them immediately. + public static void send_add_connection_metadata(IntPtr p_instance, ref metadata_frame_t p_metadata) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_add_connection_metadata_64(p_instance, ref p_metadata); + else + UnsafeNativeMethods.send_add_connection_metadata_32(p_instance, ref p_metadata); + } + + // This will assign a new fail-over source for this video source. What this means is that if this video source was to fail + // any receivers would automatically switch over to use this source, unless this source then came back online. You can specify + // NULL to clear the source. + public static void send_set_failover(IntPtr p_instance, ref source_t p_failover_source) { + if (IntPtr.Size == 8) + UnsafeNativeMethods.send_set_failover_64(p_instance, ref p_failover_source); + else + UnsafeNativeMethods.send_set_failover_32(p_instance, ref p_failover_source); + } + + [SuppressUnmanagedCodeSecurity] + internal static partial class UnsafeNativeMethods { + // send_create + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_create", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr send_create_64(ref send_create_t p_create_settings); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_create", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr send_create_32(ref send_create_t p_create_settings); + + // send_destroy + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_destroy", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_destroy_64(IntPtr p_instance); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_destroy", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_destroy_32(IntPtr p_instance); + + // send_send_video_v2 + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_send_video_v2", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_video_v2_64(IntPtr p_instance, ref video_frame_v2_t p_video_data); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_send_video_v2", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_video_v2_32(IntPtr p_instance, ref video_frame_v2_t p_video_data); + + // send_send_video_async_v2 + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_send_video_async_v2", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_video_async_v2_64(IntPtr p_instance, ref video_frame_v2_t p_video_data); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_send_video_async_v2", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_video_async_v2_32(IntPtr p_instance, ref video_frame_v2_t p_video_data); + + // send_send_audio_v2 + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_send_audio_v2", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_audio_v2_64(IntPtr p_instance, ref audio_frame_v2_t p_audio_data); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_send_audio_v2", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_audio_v2_32(IntPtr p_instance, ref audio_frame_v2_t p_audio_data); + + // send_send_metadata + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_send_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_metadata_64(IntPtr p_instance, ref metadata_frame_t p_metadata); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_send_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_send_metadata_32(IntPtr p_instance, ref metadata_frame_t p_metadata); + + // send_capture + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_capture", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern frame_type_e send_capture_64(IntPtr p_instance, ref metadata_frame_t p_metadata, UInt32 timeout_in_ms); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_capture", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern frame_type_e send_capture_32(IntPtr p_instance, ref metadata_frame_t p_metadata, UInt32 timeout_in_ms); + + // send_free_metadata + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_free_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_free_metadata_64(IntPtr p_instance, ref metadata_frame_t p_metadata); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_free_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_free_metadata_32(IntPtr p_instance, ref metadata_frame_t p_metadata); + + // send_get_tally + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_get_tally", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAsAttribute(UnmanagedType.U1)] + internal static extern bool send_get_tally_64(IntPtr p_instance, ref tally_t p_tally, UInt32 timeout_in_ms); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_get_tally", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAsAttribute(UnmanagedType.U1)] + internal static extern bool send_get_tally_32(IntPtr p_instance, ref tally_t p_tally, UInt32 timeout_in_ms); + + // send_get_no_connections + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_get_no_connections", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern int send_get_no_connections_64(IntPtr p_instance, UInt32 timeout_in_ms); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_get_no_connections", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern int send_get_no_connections_32(IntPtr p_instance, UInt32 timeout_in_ms); + + // send_clear_connection_metadata + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_clear_connection_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_clear_connection_metadata_64(IntPtr p_instance); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_clear_connection_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_clear_connection_metadata_32(IntPtr p_instance); + + // send_add_connection_metadata + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_add_connection_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_add_connection_metadata_64(IntPtr p_instance, ref metadata_frame_t p_metadata); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_add_connection_metadata", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_add_connection_metadata_32(IntPtr p_instance, ref metadata_frame_t p_metadata); + + // send_set_failover + [DllImport("Processing.NDI.Lib.x64.dll", EntryPoint = "NDIlib_send_set_failover", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_set_failover_64(IntPtr p_instance, ref source_t p_failover_source); + [DllImport("Processing.NDI.Lib.x86.dll", EntryPoint = "NDIlib_send_set_failover", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern void send_set_failover_32(IntPtr p_instance, ref source_t p_failover_source); + + } // UnsafeNativeMethods + + } // class NDIlib +#endif +} // namespace NewTek \ No newline at end of file diff --git a/NDI/Pinvoke/Processing.NDI.structs.Interop.cs b/NDI/Pinvoke/Processing.NDI.structs.Interop.cs new file mode 100644 index 000000000..e924c8f7e --- /dev/null +++ b/NDI/Pinvoke/Processing.NDI.structs.Interop.cs @@ -0,0 +1,206 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +namespace NewTek { +#if AllowUpdate + [SuppressUnmanagedCodeSecurity] + public static partial class NDIlib { + // An enumeration to specify the type of a packet returned by the functions + public enum frame_type_e { + frame_type_none = 0, + frame_type_video = 1, + frame_type_audio = 2, + frame_type_metadata = 3, + frame_type_error = 4, + + // This indicates that the settings on this input have changed. + // For instamce, this value will be returned from NDIlib_recv_capture_v2 and NDIlib_recv_capture + // when the device is known to have new settings, for instance the web-url has changed ot the device + // is now known to be a PTZ camera. + frame_type_status_change = 100 + } + + public enum FourCC_type_e { + // YCbCr color space + FourCC_type_UYVY = 0x59565955, + + // 4:2:0 formats + NDIlib_FourCC_video_type_YV12 = 0x32315659, + NDIlib_FourCC_video_type_NV12 = 0x3231564E, + NDIlib_FourCC_video_type_I420 = 0x30323449, + + // BGRA + FourCC_type_BGRA = 0x41524742, + FourCC_type_BGRX = 0x58524742, + + // RGBA + FourCC_type_RGBA = 0x41424752, + FourCC_type_RGBX = 0x58424752, + + // This is a UYVY buffer followed immediately by an alpha channel buffer. + // If the stride of the YCbCr component is "stride", then the alpha channel + // starts at image_ptr + yres*stride. The alpha channel stride is stride/2. + FourCC_type_UYVA = 0x41565955 + } + + public enum frame_format_type_e { + // A progressive frame + frame_format_type_progressive = 1, + + // A fielded frame with the field 0 being on the even lines and field 1 being + // on the odd lines/ + frame_format_type_interleaved = 0, + + // Individual fields + frame_format_type_field_0 = 2, + frame_format_type_field_1 = 3 + } + + // This is a descriptor of a NDI source available on the network. + [StructLayoutAttribute(LayoutKind.Sequential)] + public struct source_t { + // A UTF8 string that provides a user readable name for this source. + // This can be used for serialization, etc... and comprises the machine + // name and the source name on that machine. In the form + // MACHINE_NAME (NDI_SOURCE_NAME) + // If you specify this parameter either as NULL, or an EMPTY string then the + // specific ip addres adn port number from below is used. + public IntPtr p_ndi_name; + + // A UTF8 string that provides the actual network address and any parameters. + // This is not meant to be application readable and might well change in the future. + // This can be nullptr if you do not know it and the API internally will instantiate + // a finder that is used to discover it even if it is not yet available on the network. + public IntPtr p_url_address; + } + + // This describes a video frame + [StructLayoutAttribute(LayoutKind.Sequential)] + public struct video_frame_v2_t { + // The resolution of this frame + public int xres, yres; + + // What FourCC this is with. This can be two values + public FourCC_type_e FourCC; + + // What is the frame-rate of this frame. + // For instance NTSC is 30000,1001 = 30000/1001 = 29.97fps + public int frame_rate_N, frame_rate_D; + + // What is the picture aspect ratio of this frame. + // For instance 16.0/9.0 = 1.778 is 16:9 video + // 0 means square pixels + public float picture_aspect_ratio; + + // Is this a fielded frame, or is it progressive + public frame_format_type_e frame_format_type; + + // The timecode of this frame in 100ns intervals + public Int64 timecode; + + // The video data itself + public IntPtr p_data; + + // The inter line stride of the video data, in bytes. + public int line_stride_in_bytes; + + // Per frame metadata for this frame. This is a NULL terminated UTF8 string that should be + // in XML format. If you do not want any metadata then you may specify NULL here. + public IntPtr p_metadata; + + // This is only valid when receiving a frame and is specified as a 100ns time that was the exact + // moment that the frame was submitted by the sending side and is generated by the SDK. If this + // value is NDIlib_recv_timestamp_undefined then this value is not available and is NDIlib_recv_timestamp_undefined. + public Int64 timestamp; + } + + // This describes an audio frame + [StructLayoutAttribute(LayoutKind.Sequential)] + public struct audio_frame_v2_t { + // The sample-rate of this buffer + public int sample_rate; + + // The number of audio channels + public int no_channels; + + // The number of audio samples per channel + public int no_samples; + + // The timecode of this frame in 100ns intervals + public Int64 timecode; + + // The audio data + public IntPtr p_data; + + // The inter channel stride of the audio channels, in bytes + public int channel_stride_in_bytes; + + // Per frame metadata for this frame. This is a NULL terminated UTF8 string that should be + // in XML format. If you do not want any metadata then you may specify NULL here. + public IntPtr p_metadata; + + // This is only valid when receiving a frame and is specified as a 100ns time that was the exact + // moment that the frame was submitted by the sending side and is generated by the SDK. If this + // value is NDIlib_recv_timestamp_undefined then this value is not available and is NDIlib_recv_timestamp_undefined. + public Int64 timestamp; + } + + // The data description for metadata + [StructLayoutAttribute(LayoutKind.Sequential)] + public struct metadata_frame_t { + // The length of the string in UTF8 characters. This includes the NULL terminating character. + // If this is 0, then the length is assume to be the length of a NULL terminated string. + public int length; + + // The timecode of this frame in 100ns intervals + public Int64 timecode; + + // The metadata as a UTF8 XML string. This is a NULL terminated string. + public IntPtr p_data; + } + + // Tally structures + [StructLayoutAttribute(LayoutKind.Sequential)] + public struct tally_t { + // Is this currently on program output + [MarshalAsAttribute(UnmanagedType.U1)] + public bool on_program; + + // Is this currently on preview output + [MarshalAsAttribute(UnmanagedType.U1)] + public bool on_preview; + } + + // When you specify this as a timecode, the timecode will be synthesized for you. This may + // be used when sending video, audio or metadata. If you never specify a timecode at all, + // asking for each to be synthesized, then this will use the current system time as the + // starting timecode and then generate synthetic ones, keeping your streams exactly in + // sync as long as the frames you are sending do not deviate from the system time in any + // meaningful way. In practice this means that if you never specify timecodes that they + // will always be generated for you correctly. Timecodes coming from different senders on + // the same machine will always be in sync with eachother when working in this way. If you + // have NTP installed on your local network, then streams can be synchronized between + // multiple machines with very high precision. + // + // If you specify a timecode at a particular frame (audio or video), then ask for all subsequent + // ones to be synthesized. The subsequent ones will be generated to continue this sequency + // maintining the correct relationship both the between streams and samples generated, avoiding + // them deviating in time from the timecode that you specified in any meanginfful way. + // + // If you specify timecodes on one stream (e.g. video) and ask for the other stream (audio) to + // be sythesized, the correct timecodes will be generated for the other stream and will be synthesize + // exactly to match (they are not quantized inter-streams) the correct sample positions. + // + // When you send metadata messagesa and ask for the timecode to be synthesized, then it is chosen + // to match the closest audio or video frame timecode so that it looks close to something you might + // want ... unless there is no sample that looks close in which a timecode is synthesized from the + // last ones known and the time since it was sent. + // + public static Int64 send_timecode_synthesize = Int64.MaxValue; + + // If the time-stamp is not available (i.e. a version of a sender before v2.5) + public static Int64 recv_timestamp_undefined = Int64.MaxValue; + + } // class NDIlib +#endif +} // namespace NewTek \ No newline at end of file diff --git a/NDI/Sender.cs b/NDI/Sender.cs new file mode 100644 index 000000000..21fd149c0 --- /dev/null +++ b/NDI/Sender.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +namespace NewTek.NDI { +#if AllowUpdate + public class Sender : IDisposable { + public Sender(String sourceName, bool clockVideo = true, bool clockAudio = false, String[] groups = null, String failoverName = null) { + if (String.IsNullOrEmpty(sourceName)) { + throw new ArgumentException("sourceName can not be null or empty.", sourceName); + } + + if (!NDIlib.initialize()) { + if (!NDIlib.is_supported_CPU()) + throw new InvalidOperationException("CPU incompatible with NDI."); + else + throw new InvalidOperationException("Unable to initialize NDI."); + } + + // .Net interop doesn't handle UTF-8 strings, so do it manually + // These must be freed later + IntPtr sourceNamePtr = UTF.StringToUtf8(sourceName); + + IntPtr groupsNamePtr = IntPtr.Zero; + + // make a flat list of groups if needed + if (groups != null) { + StringBuilder flatGroups = new StringBuilder(); + foreach (String group in groups) { + flatGroups.Append(group); + if (group != groups.Last()) { + flatGroups.Append(','); + } + } + + groupsNamePtr = UTF.StringToUtf8(flatGroups.ToString()); + } + + // Create an NDI source description + NDIlib.send_create_t createDesc = new NDIlib.send_create_t() { + p_ndi_name = sourceNamePtr, + p_groups = groupsNamePtr, + clock_video = clockVideo, + clock_audio = clockAudio + }; + + // create the NDI send instance + _sendInstancePtr = NDIlib.send_create(ref createDesc); + + // free the strings we allocated + Marshal.FreeHGlobal(sourceNamePtr); + Marshal.FreeHGlobal(groupsNamePtr); + + // did it succeed? + if (_sendInstancePtr == IntPtr.Zero) { + throw new InvalidOperationException("Failed to create send instance."); + } + + if (!String.IsNullOrEmpty(failoverName)) { + // .Net interop doesn't handle UTF-8 strings, so do it manually + // These must be freed later + IntPtr failoverNamePtr = UTF.StringToUtf8(failoverName); + + NDIlib.source_t failoverDesc = new NDIlib.source_t() { + p_ndi_name = failoverNamePtr, + p_url_address = IntPtr.Zero + }; + + NDIlib.send_set_failover(_sendInstancePtr, ref failoverDesc); + + // free the strings we allocated + Marshal.FreeHGlobal(failoverNamePtr); + } + } + + // The current tally state + public NDIlib.tally_t Tally { + get { + if (_sendInstancePtr == IntPtr.Zero) + return _ndiTally; + + NDIlib.send_get_tally(_sendInstancePtr, ref _ndiTally, 0); + + return _ndiTally; + } + } + + // Determine the current tally sate. If you specify a timeout then it will wait until it has changed, otherwise it will simply poll it + // and return the current tally immediately. The return value is whether anything has actually change (true) or whether it timed out (false) + public bool GetTally(ref NDIlib.tally_t tally, int timeout) { + if (_sendInstancePtr == IntPtr.Zero) + return false; + + return NDIlib.send_get_tally(_sendInstancePtr, ref tally, (uint)timeout); + } + + // The number of current connections + public int Connections { + get { + if (_sendInstancePtr == IntPtr.Zero) + return 0; + + return NDIlib.send_get_no_connections(_sendInstancePtr, 0); + } + } + + // Get the current number of receivers connected to this source. This can be used to avoid even rendering when nothing is connected to the video source. + // which can significantly improve the efficiency if you want to make a lot of sources available on the network. If you specify a timeout that is not + // 0 then it will wait until there are connections for this amount of time. + public int GetConnections(int waitMs) { + if (_sendInstancePtr == IntPtr.Zero) + return 0; + + return NDIlib.send_get_no_connections(_sendInstancePtr, (uint)waitMs); + } + + public void Send(VideoFrame videoFrame) { + Send(ref videoFrame._ndiVideoFrame); + } + + public void Send(ref NDIlib.video_frame_v2_t videoFrame) { + if (_sendInstancePtr == IntPtr.Zero) + return; + + NDIlib.send_send_video_v2(_sendInstancePtr, ref videoFrame); + } + + public void Send(AudioFrame audioFrame) { + Send(ref audioFrame._ndiAudioFrame); + } + + public void Send(ref NDIlib.audio_frame_v2_t audioFrame) { + if (_sendInstancePtr == IntPtr.Zero) + return; + + NDIlib.send_send_audio_v2(_sendInstancePtr, ref audioFrame); + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~Sender() { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (_sendInstancePtr != IntPtr.Zero) { + NDIlib.send_destroy(_sendInstancePtr); + _sendInstancePtr = IntPtr.Zero; + } + + NDIlib.destroy(); + } + } + + private IntPtr _sendInstancePtr = IntPtr.Zero; + private NDIlib.tally_t _ndiTally = new NDIlib.tally_t(); + } +#endif +} \ No newline at end of file diff --git a/NDI/Source.cs b/NDI/Source.cs new file mode 100644 index 000000000..45a144455 --- /dev/null +++ b/NDI/Source.cs @@ -0,0 +1,54 @@ +using System; +using System.Text.RegularExpressions; +namespace NewTek.NDI { +#if AllowUpdate + public class Source { + public string ComputerName { get; private set; } + public string SourceName { get; private set; } + public Uri Uri { get; private set; } + // These are purposely 'public get' only because + // they should not change during the life of a source. + private string _name = string.Empty; + public string Name { + get { return _name; } + private set { + _name = value; + + int parenIdx = _name.IndexOf(" ("); + ComputerName = _name.Substring(0, parenIdx); + + SourceName = Regex.Match(_name, @"(?<=\().+?(?=\))").Value; + + string uriString = string.Format("ndi://{0}/{1}", ComputerName, System.Net.WebUtility.UrlEncode(SourceName)); + + Uri uri = null; + if (!Uri.TryCreate(uriString, UriKind.Absolute, out uri)) { + Uri = null; + } else { + Uri = uri; + } + + } + } + + // Construct from NDIlib.source_t + public Source(NDIlib.source_t source_t) { + Name = UTF.Utf8ToString(source_t.p_ndi_name); + } + + // Construct from strings + public Source(string name) { + Name = name; + } + + // Copy constructor. + public Source(Source previousSource) { + Name = previousSource.Name; + Uri = previousSource.Uri; + } + public override string ToString() { + return Name; + } + } +#endif +} \ No newline at end of file diff --git a/NDI/Utilities.cs b/NDI/Utilities.cs new file mode 100644 index 000000000..da4c68d41 --- /dev/null +++ b/NDI/Utilities.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +// Utility functions outside of the NDILib SDK itself, +// but useful for working with NDI from managed languages. +namespace NewTek.NDI { +#if AllowUpdate + [SuppressUnmanagedCodeSecurity] + public static partial class UTF { + // This REQUIRES you to use Marshal.FreeHGlobal() on the returned pointer! + public static IntPtr StringToUtf8(String managedString) { + int len = Encoding.UTF8.GetByteCount(managedString); + + byte[] buffer = new byte[len + 1]; + + Encoding.UTF8.GetBytes(managedString, 0, managedString.Length, buffer, 0); + + IntPtr nativeUtf8 = Marshal.AllocHGlobal(buffer.Length); + + Marshal.Copy(buffer, 0, nativeUtf8, buffer.Length); + + return nativeUtf8; + } + + // this version will also return the length of the utf8 string + // This REQUIRES you to use Marshal.FreeHGlobal() on the returned pointer! + public static IntPtr StringToUtf8(String managedString, out int utf8Length) { + utf8Length = Encoding.UTF8.GetByteCount(managedString); + + byte[] buffer = new byte[utf8Length + 1]; + + Encoding.UTF8.GetBytes(managedString, 0, managedString.Length, buffer, 0); + + IntPtr nativeUtf8 = Marshal.AllocHGlobal(buffer.Length); + + Marshal.Copy(buffer, 0, nativeUtf8, buffer.Length); + + return nativeUtf8; + } + + // Length is optional, but recommended + // This is all potentially dangerous + public static string Utf8ToString(IntPtr nativeUtf8, uint? length = null) { + if (nativeUtf8 == IntPtr.Zero) + return String.Empty; + + uint len = 0; + + if (length.HasValue) { + len = length.Value; + } else { + // try to find the terminator + while (Marshal.ReadByte(nativeUtf8, (int)len) != 0) { + ++len; + } + } + + byte[] buffer = new byte[len]; + + Marshal.Copy(nativeUtf8, buffer, 0, buffer.Length); + + return Encoding.UTF8.GetString(buffer); + } + + } // class NDILib +#endif +} // namespace NewTek.NDI \ No newline at end of file diff --git a/NDI/VideoFrame.cs b/NDI/VideoFrame.cs new file mode 100644 index 000000000..6160e0f44 --- /dev/null +++ b/NDI/VideoFrame.cs @@ -0,0 +1,124 @@ +using System; +using System.Runtime.InteropServices; +using System.Xml.Linq; +namespace NewTek.NDI { +#if AllowUpdate + public class VideoFrame : IDisposable { + // the simple constructor only deals with BGRA. For other color formats you'll need to handle it manually. + // Defaults to progressive but can be changed. + public VideoFrame(int width, int height, float aspectRatio, int frameRateNumerator, int frameRateDenominator, + NDIlib.frame_format_type_e format = NDIlib.frame_format_type_e.frame_format_type_progressive) { + // we have to know to free it later + _memoryOwned = true; + + int stride = (width * 32 /*BGRA bpp*/ + 7) / 8; + int bufferSize = height * stride; + + // allocate some memory for a video buffer + IntPtr videoBufferPtr = Marshal.AllocHGlobal(bufferSize); + + _ndiVideoFrame = new NDIlib.video_frame_v2_t() { + xres = width, + yres = height, + FourCC = NDIlib.FourCC_type_e.FourCC_type_BGRA, + frame_rate_N = frameRateNumerator, + frame_rate_D = frameRateDenominator, + picture_aspect_ratio = aspectRatio, + frame_format_type = format, + timecode = NDIlib.send_timecode_synthesize, + p_data = videoBufferPtr, + line_stride_in_bytes = stride, + p_metadata = IntPtr.Zero, + timestamp = 0 + }; + } + + public VideoFrame(IntPtr bufferPtr, int width, int height, int stride, NDIlib.FourCC_type_e fourCC, + float aspectRatio, int frameRateNumerator, int frameRateDenominator, NDIlib.frame_format_type_e format) { + _ndiVideoFrame = new NDIlib.video_frame_v2_t() { + xres = width, + yres = height, + FourCC = fourCC, + frame_rate_N = frameRateNumerator, + frame_rate_D = frameRateDenominator, + picture_aspect_ratio = aspectRatio, + frame_format_type = format, + timecode = NDIlib.send_timecode_synthesize, + p_data = bufferPtr, + line_stride_in_bytes = stride, + p_metadata = IntPtr.Zero, + timestamp = 0 + }; + } + + public int Width { + get { + return _ndiVideoFrame.xres; + } + } + + public int Height { + get { + return _ndiVideoFrame.yres; + } + } + + public int Stride { + get { + return _ndiVideoFrame.line_stride_in_bytes; + } + } + + public IntPtr BufferPtr { + get { + return _ndiVideoFrame.p_data; + } + } + + public Int64 TimeStamp { + get { + return _ndiVideoFrame.timestamp; + } + set { + _ndiVideoFrame.timestamp = value; + } + } + + public XElement MetaData { + get { + if (_ndiVideoFrame.p_metadata == IntPtr.Zero) + return null; + + String mdString = UTF.Utf8ToString(_ndiVideoFrame.p_metadata); + if (String.IsNullOrEmpty(mdString)) + return null; + + return XElement.Parse(mdString); + } + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~VideoFrame() { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (_memoryOwned) { + Marshal.FreeHGlobal(_ndiVideoFrame.p_data); + _ndiVideoFrame.p_data = IntPtr.Zero; + } + + NDIlib.destroy(); + } + } + + internal NDIlib.video_frame_v2_t _ndiVideoFrame; + bool _memoryOwned = false; + } +#endif +} \ No newline at end of file diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index fabc4475e..71244e5c2 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -1,14 +1,14 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("FallGuysStats")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyTitle("Fall Guys Stats Tracker")] +[assembly: AssemblyDescription("Program that will generate statistics for Fall Guys")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FallGuysStats")] +[assembly: AssemblyCompany("Fall Guys Stats")] +[assembly: AssemblyProduct("Fall Guys Stats")] [assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("9ddf7bd8-3c77-43d4-a229-f79ce082c6e8")] -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.107.0.0")] +[assembly: AssemblyFileVersion("1.107.0.0")] \ No newline at end of file diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index cfd44f6fe..9e6a90c67 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -63,9 +63,19 @@ internal Resources() { /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap info { + internal static System.Drawing.Bitmap background { get { - object obj = ResourceManager.GetObject("info", resourceCulture); + object obj = ResourceManager.GetObject("background", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap export { + get { + object obj = ResourceManager.GetObject("export", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } @@ -119,5 +129,25 @@ internal static System.Drawing.Bitmap medal_silver { return ((System.Drawing.Bitmap)(obj)); } } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap tab_selected { + get { + object obj = ResourceManager.GetObject("tab_selected", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap tab_unselected { + get { + object obj = ResourceManager.GetObject("tab_unselected", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } } } diff --git a/Properties/Resources.resx b/Properties/Resources.resx index 2576a8752..e11284ffb 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -118,22 +118,31 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\info.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\medal_pink.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\medal_gold.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\background.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\medal_bronze.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\medal_bronze.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\medal_eliminated.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\medal_eliminated.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\medal_gold.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\medal_silver.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\medal_pink.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\tab_selected.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\medal_silver.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\tab_unselected.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\export.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/Properties/levelWindow.png b/Properties/levelWindow.png new file mode 100644 index 000000000..e90a7df92 Binary files /dev/null and b/Properties/levelWindow.png differ diff --git a/Properties/mainWindow.png b/Properties/mainWindow.png new file mode 100644 index 000000000..7f251b37b Binary files /dev/null and b/Properties/mainWindow.png differ diff --git a/Properties/overlay.png b/Properties/overlay.png new file mode 100644 index 000000000..b3271eaaf Binary files /dev/null and b/Properties/overlay.png differ diff --git a/Properties/showsWindow.png b/Properties/showsWindow.png new file mode 100644 index 000000000..dc96a28b4 Binary files /dev/null and b/Properties/showsWindow.png differ diff --git a/README.md b/README.md index 89028e50c..5bcbccc8e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,182 @@ # Fall Guys Stats Simple program to generate stats for the game Fall Guys. Reads the games log file to track how you are doing. -## Info +## Download + - [FallGuyStats.zip](https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/FallGuyStats.zip) + + - or if you have problems with false positives in your virus program this one removes the ability to auto update [FallGuyStatsManualUpdate.zip](https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/FallGuyStatsManualUpdate.zip) + +## Usage + - Extract zip to it's own folder - Run the program while playing Fall Guys to see new stats. - - Only updates after a show has been completed and results are given. \ No newline at end of file + - Only updates after a show has been completed and results are given. + +![Fall Guys Stats](https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/Properties/mainWindow.png) + +![Fall Guys Level Stats](https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/Properties/levelWindow.png) + +## Overlay +![Overlay](https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/Properties/overlay.png) + + - Hit 'T' to toggle background colors + - Hit 'F' to flip the Display + +## Deleting Shows +![Shows](https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/Properties/showsWindow.png) + + - Click the blue Shows label on the main screen + - Highlight any number of shows and hit the 'DEL' key + +## Changelog + - 1.107 + - Add Font Chooser for overlay + - Fix Average times on main grid + - 1.106 + - Added better options for the cycle stats on overlay in settings + - Added average finish time to main grid + - Minor sorting fixes + - 1.105 + - Grid sorting improvements + - Display issue with private lobby stats on overlay + - 1.104 + - Implemented a number of improvements from hunterpankey + - 1.103 + - Fix log reading during live rounds + - 1.102 + - Fix issues reading log file in certain cases + - Made sure private lobbies stats dont show in main screen + - 1.101 + - Add ability to track private lobbies + - 1.100 + - Fixes and added levels for Season 3 + - 1.99 + - Hopefully made it so game modes wont affect levels anymore + - 1.98 + - Logic to handle new game mode + - Added ability to only show certain stats on overlay instead of having to cycle them + - 1.97 + - Logic to handle new game mode + - 1.96 + - Fixed existing levels for the northernlion game mode to show up correctly + - 1.95 + - Fixed new game mode adding levels that shouldn't be there + - 1.94 + - Fixed typo in level name + - 1.93 + - Added ability to rename Hoopsie Legends to Hoopsie Heroes + - Added logic to save main window size + - 1.92 + - Added code to handle levels with variations in their name + - 1.91 + - Added Big Fans Level + - 1.90 + - Fixed names on overlay + - 1.89 + - Fixed names for new Slime Event levels + - 1.88 + - Added more info to AssemblyInfo to possibly help with false positives in AV programs + - 1.87 + - Fixed level names in level details for gauntlet matches + - Allowed main window to be resizable + - 1.86 + - Fixed Level stats grid columns + - 1.85 + - Finish time on overlay will now become gold when you beat overall best time or green when you beat best time for current filter + - Time on overlay will now also show the timeout duration + - 1.84 + - Fixed a filter issue with profiles + - 1.83 + - Added ability to switch between a Main and Practice profile + - 1.82 + - Fixed season filter dates + - 1.81 + - Fixed guantlet levels not showing up on Overlay properly + - 1.80 + - Added Final Streak to cycle with Win Streak + - Added new maps + - 1.79 + - Added option to cycle between Players and Server Ping on overlay + - 1.78 + - Changed logic when not cycling stats on overlay to show the most interesting stat + - Added option to show / hide percentages on overlay + - 1.77 + - Added individual option for Cycle Qualify / Gold and Cycle Fastest / Longest to settings + - 1.76 + - Moved Season 2 start date to Oct 8th + - Added ability to choose when starting program to include previous stats or not + - 1.75 + - Fixed streak count on overlay + - 1.74 + - Fixed stat calculations for shows crossing filter boundries + - Added some extra stats to the Wins Per Day popup + - Added option in settings to show / hide Wins info for overlay + - 1.73 + - Added options to settings screen for overlay color and flip to make it more visible to the user + - Added ability to manually resize overlay from the corners + - 1.72 + - Changed overlay so it stays visible when you minimize amin screen + - 1.71 + - Changed main screen to show Fastest / Longest qualifications for each level + - Fixed minor sorting issue in the grids + - 1.70 + - Cleaned up auto update feature a bit + - 1.69 + - Program will save last location of main window now and restore it when opened again + - 1.68 + - Fixed Week / Day filters + - Added more filter options in settings + - Added logic to account for new levels that may come up in Season 2 + - Added option to auto update program in settings + - 1.67 + - Fixed times in database to be stored correctly as utc + - 1.66 + - Hopefully fixed an issue with times counting down on overlay in rare cases + - 1.65 + - Added export to MarkDown option + - Added ability to click on Win% label to toggle columns to show %s and back + - 1.64 + - Fixed time issue when parsing log + - 1.63 + - Added export options for both Html and BBCode when right clicking any grid + - 1.62 + - Fixed some logic when deleting shows while filtered + - Switched the Longest/Fastest to align with Qualify/Gold + - 1.61 + - Added logic to reset overlay position if it ended up off screen + - Tightened up the overlay when hiding the round info + - 1.60 + - Added option to show tab on overlay for current filter + - 1.59 + - Try and make sure deleted shows dont get added back accidentally + - 1.58 + - Fixed rare case when deleting show didnt work + - 1.57 + - Fixed overlay missing image on startup + - 1.56 + - Add ability to show / hide information on overlay + - 1.55 + - Fixed overlay getting out of wack if you change filters a lot + - 1.54 + - Added mouse hover tool tip on Qualified / Gold / Silver / Bronze to show value as a % + - 1.53 + - Fixed Filters on overlay not taking into account UTC time + - 1.52 + - Fixed Time display on overlay not updating sometimes + - 1.51 + - Fixed an issue around results coming from a previous match + - 1.50 + - Fixed accidental debug typo + - 1.49 + - Added filter options to settings page for overlay display + - 1.48 + - Fixed Gold statistic on overlay, was using wrong medal type + - 1.47 + - Added Gold statistic to overlay that rotates with Qualify + - 1.46 + - Fixed overlay display not updating + - 1.45 + - Cleaned up labels on overlay + - 1.44 + - Fixed end of round numbers on overlay + - 1.43 + - Added ability to delete Shows in Shows screen \ No newline at end of file diff --git a/Resources/TitanOne-Regular.ttf b/Resources/TitanOne-Regular.ttf new file mode 100644 index 000000000..c615c9be4 Binary files /dev/null and b/Resources/TitanOne-Regular.ttf differ diff --git a/Resources/background.png b/Resources/background.png new file mode 100644 index 000000000..f59cc4b9d Binary files /dev/null and b/Resources/background.png differ diff --git a/Resources/export.png b/Resources/export.png new file mode 100644 index 000000000..47e079535 Binary files /dev/null and b/Resources/export.png differ diff --git a/fall.ico b/Resources/fall.ico similarity index 100% rename from fall.ico rename to Resources/fall.ico diff --git a/medal_bronze.png b/Resources/medal_bronze.png similarity index 100% rename from medal_bronze.png rename to Resources/medal_bronze.png diff --git a/medal_eliminated.png b/Resources/medal_eliminated.png similarity index 100% rename from medal_eliminated.png rename to Resources/medal_eliminated.png diff --git a/medal_gold.png b/Resources/medal_gold.png similarity index 100% rename from medal_gold.png rename to Resources/medal_gold.png diff --git a/medal_pink.png b/Resources/medal_pink.png similarity index 100% rename from medal_pink.png rename to Resources/medal_pink.png diff --git a/medal_silver.png b/Resources/medal_silver.png similarity index 100% rename from medal_silver.png rename to Resources/medal_silver.png diff --git a/Resources/tab_selected.png b/Resources/tab_selected.png new file mode 100644 index 000000000..819ae58d7 Binary files /dev/null and b/Resources/tab_selected.png differ diff --git a/Resources/tab_unselected.png b/Resources/tab_unselected.png new file mode 100644 index 000000000..ad61d942c Binary files /dev/null and b/Resources/tab_unselected.png differ diff --git a/Stats.Designer.cs b/Stats.Designer.cs deleted file mode 100644 index bbe82022d..000000000 --- a/Stats.Designer.cs +++ /dev/null @@ -1,233 +0,0 @@ -namespace FallGuysStats { - partial class Stats { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) { - if (disposing && (components != null)) { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Stats)); - this.lblTotalShows = new System.Windows.Forms.Label(); - this.lblTotalTime = new System.Windows.Forms.Label(); - this.lblTotalRounds = new System.Windows.Forms.Label(); - this.lblTotalWins = new System.Windows.Forms.Label(); - this.lblFinalChance = new System.Windows.Forms.Label(); - this.lblWinChance = new System.Windows.Forms.Label(); - this.gridDetails = new FallGuysStats.Grid(); - this.rdAll = new System.Windows.Forms.RadioButton(); - this.rdSeason = new System.Windows.Forms.RadioButton(); - this.rdWeek = new System.Windows.Forms.RadioButton(); - this.rdSession = new System.Windows.Forms.RadioButton(); - ((System.ComponentModel.ISupportInitialize)(this.gridDetails)).BeginInit(); - this.SuspendLayout(); - // - // lblTotalShows - // - this.lblTotalShows.AutoSize = true; - this.lblTotalShows.Location = new System.Drawing.Point(145, 34); - this.lblTotalShows.Name = "lblTotalShows"; - this.lblTotalShows.Size = new System.Drawing.Size(51, 13); - this.lblTotalShows.TabIndex = 5; - this.lblTotalShows.Text = "Shows: 0"; - // - // lblTotalTime - // - this.lblTotalTime.AutoSize = true; - this.lblTotalTime.Location = new System.Drawing.Point(8, 34); - this.lblTotalTime.Name = "lblTotalTime"; - this.lblTotalTime.Size = new System.Drawing.Size(113, 13); - this.lblTotalTime.TabIndex = 4; - this.lblTotalTime.Text = "Time Played: 00:00:00"; - // - // lblTotalRounds - // - this.lblTotalRounds.AutoSize = true; - this.lblTotalRounds.Location = new System.Drawing.Point(231, 34); - this.lblTotalRounds.Name = "lblTotalRounds"; - this.lblTotalRounds.Size = new System.Drawing.Size(56, 13); - this.lblTotalRounds.TabIndex = 6; - this.lblTotalRounds.Text = "Rounds: 0"; - // - // lblTotalWins - // - this.lblTotalWins.AutoSize = true; - this.lblTotalWins.Location = new System.Drawing.Point(323, 34); - this.lblTotalWins.Name = "lblTotalWins"; - this.lblTotalWins.Size = new System.Drawing.Size(43, 13); - this.lblTotalWins.TabIndex = 7; - this.lblTotalWins.Text = "Wins: 0"; - // - // lblFinalChance - // - this.lblFinalChance.AutoSize = true; - this.lblFinalChance.Location = new System.Drawing.Point(399, 34); - this.lblFinalChance.Name = "lblFinalChance"; - this.lblFinalChance.Size = new System.Drawing.Size(52, 13); - this.lblFinalChance.TabIndex = 8; - this.lblFinalChance.Text = "Final %: 0"; - // - // lblWinChance - // - this.lblWinChance.AutoSize = true; - this.lblWinChance.Location = new System.Drawing.Point(492, 34); - this.lblWinChance.Name = "lblWinChance"; - this.lblWinChance.Size = new System.Drawing.Size(49, 13); - this.lblWinChance.TabIndex = 9; - this.lblWinChance.Text = "Win %: 0"; - // - // gridDetails - // - this.gridDetails.AllowUserToDeleteRows = false; - this.gridDetails.AllowUserToOrderColumns = false; - this.gridDetails.AllowUserToResizeColumns = false; - this.gridDetails.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.gridDetails.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; - this.gridDetails.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.gridDetails.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single; - dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle1.BackColor = System.Drawing.Color.LightGray; - dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - dataGridViewCellStyle1.ForeColor = System.Drawing.Color.Black; - dataGridViewCellStyle1.SelectionBackColor = System.Drawing.Color.Cyan; - dataGridViewCellStyle1.SelectionForeColor = System.Drawing.Color.Black; - dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.gridDetails.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1; - this.gridDetails.ColumnHeadersHeight = 20; - this.gridDetails.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.DisableResizing; - dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle2.BackColor = System.Drawing.Color.White; - dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - dataGridViewCellStyle2.ForeColor = System.Drawing.Color.Black; - dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.White; - dataGridViewCellStyle2.SelectionForeColor = System.Drawing.Color.Black; - dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; - this.gridDetails.DefaultCellStyle = dataGridViewCellStyle2; - this.gridDetails.EnableHeadersVisualStyles = false; - this.gridDetails.Location = new System.Drawing.Point(0, 53); - this.gridDetails.Name = "gridDetails"; - this.gridDetails.ReadOnly = true; - this.gridDetails.RowHeadersVisible = false; - this.gridDetails.Size = new System.Drawing.Size(614, 570); - this.gridDetails.TabIndex = 10; - this.gridDetails.TabStop = false; - this.gridDetails.DataSourceChanged += new System.EventHandler(this.gridDetails_DataSourceChanged); - this.gridDetails.CellClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.gridDetails_CellClick); - this.gridDetails.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.gridDetails_CellFormatting); - this.gridDetails.CellMouseEnter += new System.Windows.Forms.DataGridViewCellEventHandler(this.gridDetails_CellMouseEnter); - // - // rdAll - // - this.rdAll.AutoSize = true; - this.rdAll.Checked = true; - this.rdAll.Location = new System.Drawing.Point(12, 9); - this.rdAll.Name = "rdAll"; - this.rdAll.Size = new System.Drawing.Size(63, 17); - this.rdAll.TabIndex = 0; - this.rdAll.TabStop = true; - this.rdAll.Text = "All Stats"; - this.rdAll.UseVisualStyleBackColor = true; - this.rdAll.CheckedChanged += new System.EventHandler(this.rdAll_CheckedChanged); - // - // rdSeason - // - this.rdSeason.AutoSize = true; - this.rdSeason.Location = new System.Drawing.Point(81, 9); - this.rdSeason.Name = "rdSeason"; - this.rdSeason.Size = new System.Drawing.Size(61, 17); - this.rdSeason.TabIndex = 1; - this.rdSeason.Text = "Season"; - this.rdSeason.UseVisualStyleBackColor = true; - this.rdSeason.CheckedChanged += new System.EventHandler(this.rdAll_CheckedChanged); - // - // rdWeek - // - this.rdWeek.AutoSize = true; - this.rdWeek.Location = new System.Drawing.Point(147, 9); - this.rdWeek.Name = "rdWeek"; - this.rdWeek.Size = new System.Drawing.Size(54, 17); - this.rdWeek.TabIndex = 2; - this.rdWeek.Text = "Week"; - this.rdWeek.UseVisualStyleBackColor = true; - this.rdWeek.CheckedChanged += new System.EventHandler(this.rdAll_CheckedChanged); - // - // rdSession - // - this.rdSession.AutoSize = true; - this.rdSession.Location = new System.Drawing.Point(207, 9); - this.rdSession.Name = "rdSession"; - this.rdSession.Size = new System.Drawing.Size(62, 17); - this.rdSession.TabIndex = 3; - this.rdSession.Text = "Session"; - this.rdSession.UseVisualStyleBackColor = true; - this.rdSession.CheckedChanged += new System.EventHandler(this.rdAll_CheckedChanged); - // - // Stats - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(242)))), ((int)(((byte)(251))))); - this.ClientSize = new System.Drawing.Size(614, 623); - this.Controls.Add(this.rdSession); - this.Controls.Add(this.rdWeek); - this.Controls.Add(this.rdSeason); - this.Controls.Add(this.rdAll); - this.Controls.Add(this.lblWinChance); - this.Controls.Add(this.lblTotalTime); - this.Controls.Add(this.lblFinalChance); - this.Controls.Add(this.lblTotalShows); - this.Controls.Add(this.lblTotalWins); - this.Controls.Add(this.lblTotalRounds); - this.Controls.Add(this.gridDetails); - this.DoubleBuffered = true; - this.ForeColor = System.Drawing.Color.Black; - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.Name = "Stats"; - this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Fall Guys Stats v1.5"; - this.Shown += new System.EventHandler(this.Stats_Shown); - ((System.ComponentModel.ISupportInitialize)(this.gridDetails)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - private Grid gridDetails; - private System.Windows.Forms.Label lblTotalShows; - private System.Windows.Forms.Label lblTotalTime; - private System.Windows.Forms.Label lblTotalRounds; - private System.Windows.Forms.Label lblTotalWins; - private System.Windows.Forms.Label lblFinalChance; - private System.Windows.Forms.Label lblWinChance; - private System.Windows.Forms.RadioButton rdAll; - private System.Windows.Forms.RadioButton rdSeason; - private System.Windows.Forms.RadioButton rdWeek; - private System.Windows.Forms.RadioButton rdSession; - } -} - diff --git a/Stats.cs b/Stats.cs deleted file mode 100644 index 3cc76e8a2..000000000 --- a/Stats.cs +++ /dev/null @@ -1,263 +0,0 @@ -using LiteDB; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Windows.Forms; -namespace FallGuysStats { - public partial class Stats : Form { - [STAThread] - static void Main() { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Stats()); - } - - private static DateTime SeasonStart = new DateTime(2020, 8, 2, 0, 0, 0, DateTimeKind.Local); - private static DateTime WeekStart = DateTime.SpecifyKind(DateTime.Now.AddDays(-7).ToUniversalTime(), DateTimeKind.Local); - private static DateTime SessionStart = DateTime.SpecifyKind(DateTime.Now.ToUniversalTime(), DateTimeKind.Local); - - private List details = new List(); - private Dictionary lookup = new Dictionary(); - private LogFileWatcher logFile = new LogFileWatcher(); - private int Shows; - private int Rounds; - private TimeSpan Duration; - private int Wins; - private int Finals; - private string logPath; - private int nextShowID; - private bool loadingExisting; - private LiteDatabase statsDB; - private ILiteCollection roundDetails; - public Stats() { - InitializeComponent(); - - logFile.OnParsedLogLines += LogFile_OnParsedLogLines; - - details.Add(new LevelStats("Door Dash", "round_door_dash")); - details.Add(new LevelStats("Dizzy Heights", "round_gauntlet_02")); - details.Add(new LevelStats("Fruit Chute", "round_dodge_fall")); - details.Add(new LevelStats("Gate Crash", "round_chompchomp")); - details.Add(new LevelStats("Hit Parade", "round_gauntlet_01")); - details.Add(new LevelStats("Sea Saw", "round_see_saw")); - details.Add(new LevelStats("Slime Climb", "round_lava")); - details.Add(new LevelStats("Tip Toe", "round_tip_toe")); - details.Add(new LevelStats("Whirlygig", "round_gauntlet_03")); - - details.Add(new LevelStats("Block Party", "round_block_party")); - details.Add(new LevelStats("Jump Club", "round_jump_club")); - details.Add(new LevelStats("Perfect Match", "round_match_fall")); - details.Add(new LevelStats("Roll Out", "round_tunnel")); - details.Add(new LevelStats("Tail Tag", "round_tail_tag")); - - details.Add(new LevelStats("Egg Scramble", "round_egg_grab")); - details.Add(new LevelStats("Fall Ball", "round_fall_ball_60_players")); - details.Add(new LevelStats("Hoarders", "round_ballhogs")); - details.Add(new LevelStats("Hoopsie Daisy", "round_hoops")); - details.Add(new LevelStats("Jinxed", "round_jinxed")); - details.Add(new LevelStats("Rock'N'Roll", "round_rocknroll")); - details.Add(new LevelStats("Team Tail Tag", "round_conveyor_arena")); - - details.Add(new LevelStats("Fall Mountain", "round_fall_mountain_hub_complete")); - details.Add(new LevelStats("Hex-A-Gone", "round_floor_fall")); - details.Add(new LevelStats("Jump Showdown", "round_jump_showdown")); - details.Add(new LevelStats("Royal Fumble", "round_royal_rumble")); - - for (int i = 0; i < details.Count; i++) { - LevelStats calculator = details[i]; - lookup.Add(calculator.LevelName, calculator); - } - - gridDetails.DataSource = details; - - statsDB = new LiteDatabase(@"data.db"); - roundDetails = statsDB.GetCollection("RoundDetails"); - } - private void Stats_Shown(object sender, EventArgs e) { - if (roundDetails.Count() > 0) { - nextShowID = roundDetails.Max(x => x.ShowID); - List rounds = new List(); - rounds.AddRange(roundDetails.FindAll()); - loadingExisting = true; - LogFile_OnParsedLogLines(rounds); - loadingExisting = false; - } - - logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "Low", "Mediatonic", "FallGuys_client"); - logFile.Start(logPath, "Player.log"); - } - private void LogFile_OnParsedLogLines(List round) { - if (!loadingExisting) { statsDB.BeginTrans(); } - - foreach (RoundInfo stat in round) { - if (!loadingExisting) { - RoundInfo info = roundDetails.FindOne(x => x.Start == stat.Start && x.Name == stat.Name); - if (info == null) { - if (stat.Round == 1) { - nextShowID++; - } - stat.ShowID = nextShowID; - - roundDetails.Insert(stat); - } else { - continue; - } - } - - if (stat.Round == 1) { - Shows++; - } - Rounds++; - Duration += stat.End - stat.Start; - switch (stat.Name) { - case "round_fall_mountain_hub_complete": - case "round_floor_fall": - case "round_jump_showdown": - case "round_royal_rumble": - Finals++; - if (stat.Position == 1) { - Wins++; - } - break; - } - - if (lookup.ContainsKey(stat.Name)) { - stat.ToLocalTime(); - lookup[stat.Name].Add(stat); - } - } - - if (!loadingExisting) { statsDB.Commit(); } - this.Invoke((Action)UpdateTotals); - } - private void ClearTotals() { - Rounds = 0; - Duration = TimeSpan.Zero; - Wins = 0; - Shows = 0; - Finals = 0; - } - private void UpdateTotals() { - lblTotalRounds.Text = $"Rounds: {Rounds}"; - lblTotalShows.Text = $"Shows: {Shows}"; - lblTotalTime.Text = $"Time Played: {Duration:h\\:mm\\:ss}"; - lblTotalWins.Text = $"Wins: {Wins}"; - float finalChance = (float)Finals * 100 / (Shows == 0 ? 1 : Shows); - lblFinalChance.Text = $"Final %: {finalChance:0.0}"; - float winChance = (float)Wins * 100 / (Shows == 0 ? 1 : Shows); - lblWinChance.Text = $"Win %: {winChance:0.0}"; - gridDetails.Refresh(); - } - private void gridDetails_DataSourceChanged(object sender, EventArgs e) { - int pos = 0; - gridDetails.Columns.Add(new DataGridViewImageColumn() { Name = "Info", ImageLayout = DataGridViewImageCellLayout.Zoom, ToolTipText = "Level Info" }); - gridDetails.Setup("Name", pos++, 0, "Level Name", DataGridViewContentAlignment.MiddleLeft); - gridDetails.Setup("Info", pos++, 20, "", DataGridViewContentAlignment.MiddleCenter); - gridDetails.Setup("Played", pos++, 50, "Played", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Qualified", pos++, 60, "Qualified", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Gold", pos++, 50, "Gold", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Silver", pos++, 50, "Silver", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Bronze", pos++, 50, "Bronze", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("Kudos", pos++, 60, "Kudos", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("AveKudos", pos++, 70, "Avg Kudos", DataGridViewContentAlignment.MiddleRight); - gridDetails.Setup("AveDuration", pos++, 80, "Avg Duration", DataGridViewContentAlignment.MiddleRight); - } - private void gridDetails_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { - if (gridDetails.Columns[e.ColumnIndex].Name == "Name") { - switch ((string)e.Value) { - case "Door Dash": - case "Dizzy Heights": - case "Fruit Chute": - case "Gate Crash": - case "Hit Parade": - case "Sea Saw": - case "Slime Climb": - case "Tip Toe": - case "Whirlygig": - e.CellStyle.BackColor = Color.LightGoldenrodYellow; - break; - case "Block Party": - case "Jump Club": - case "Perfect Match": - case "Roll Out": - case "Tail Tag": - e.CellStyle.BackColor = Color.LightBlue; - break; - case "Egg Scramble": - case "Fall Ball": - case "Hoarders": - case "Hoopsie Daisy": - case "Jinxed": - case "Rock'N'Roll": - case "Team Tail Tag": - e.CellStyle.BackColor = Color.LightGreen; - break; - case "Fall Mountain": - case "Hex-A-Gone": - case "Jump Showdown": - case "Royal Fumble": - e.CellStyle.BackColor = Color.Pink; - break; - } - } else if (gridDetails.Columns[e.ColumnIndex].Name == "Info" && e.Value == null) { - gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = "Click to view level stats"; - e.Value = Properties.Resources.info; - } else if (gridDetails.Columns[e.ColumnIndex].Name == "AveDuration") { - LevelStats info = gridDetails.Rows[e.RowIndex].DataBoundItem as LevelStats; - e.Value = info.AveDuration.ToString("m\\:ss"); - } - } - private void gridDetails_CellMouseEnter(object sender, DataGridViewCellEventArgs e) { - if (gridDetails.Columns[e.ColumnIndex].Name == "Info") { - gridDetails.Cursor = Cursors.Hand; - } else { - gridDetails.Cursor = Cursors.Default; - } - } - private void gridDetails_CellClick(object sender, DataGridViewCellEventArgs e) { - if (gridDetails.Columns[e.ColumnIndex].Name == "Info") { - using (LevelDetails details = new LevelDetails()) { - LevelStats stats = gridDetails.Rows[e.RowIndex].DataBoundItem as LevelStats; - details.LevelName = stats.Name; - List rounds = stats.Stats; - rounds.Sort(delegate (RoundInfo one, RoundInfo two) { - return one.Start.CompareTo(two.Start); - }); - details.RoundDetails = rounds; - details.ShowDialog(this); - } - } - } - private void rdAll_CheckedChanged(object sender, EventArgs e) { - RadioButton button = sender as RadioButton; - if (!button.Checked) { return; } - - for (int i = 0; i < details.Count; i++) { - LevelStats calculator = details[i]; - calculator.Clear(); - } - - ClearTotals(); - - List rounds = new List(); - if (button == rdAll) { - rounds.AddRange(roundDetails.FindAll()); - } else if (button == rdSeason) { - rounds.AddRange(roundDetails.Find(x => x.Start > SeasonStart)); - } else if (button == rdWeek) { - rounds.AddRange(roundDetails.Find(x => x.Start > WeekStart)); - } else { - rounds.AddRange(roundDetails.Find(x => x.Start > SessionStart)); - } - - rounds.Sort(delegate (RoundInfo one, RoundInfo two) { - return one.Start.CompareTo(two.Start); - }); - - loadingExisting = true; - LogFile_OnParsedLogLines(rounds); - loadingExisting = false; - } - } -} \ No newline at end of file diff --git a/LevelDetails.Designer.cs b/Views/LevelDetails.Designer.cs similarity index 92% rename from LevelDetails.Designer.cs rename to Views/LevelDetails.Designer.cs index 4fa5516a4..014e06068 100644 --- a/LevelDetails.Designer.cs +++ b/Views/LevelDetails.Designer.cs @@ -34,8 +34,6 @@ private void InitializeComponent() { // gridDetails // this.gridDetails.AllowUserToDeleteRows = false; - this.gridDetails.AllowUserToOrderColumns = false; - this.gridDetails.AllowUserToResizeColumns = false; this.gridDetails.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; this.gridDetails.BorderStyle = System.Windows.Forms.BorderStyle.None; this.gridDetails.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single; @@ -53,7 +51,7 @@ private void InitializeComponent() { dataGridViewCellStyle2.BackColor = System.Drawing.Color.White; dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); dataGridViewCellStyle2.ForeColor = System.Drawing.Color.Black; - dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.White; + dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.DeepSkyBlue; dataGridViewCellStyle2.SelectionForeColor = System.Drawing.Color.Black; dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; this.gridDetails.DefaultCellStyle = dataGridViewCellStyle2; @@ -61,28 +59,32 @@ private void InitializeComponent() { this.gridDetails.EnableHeadersVisualStyles = false; this.gridDetails.Location = new System.Drawing.Point(0, 0); this.gridDetails.Name = "gridDetails"; + this.gridDetails.ReadOnly = true; this.gridDetails.RowHeadersVisible = false; - this.gridDetails.Size = new System.Drawing.Size(560, 504); + this.gridDetails.Size = new System.Drawing.Size(614, 509); this.gridDetails.TabIndex = 10; this.gridDetails.DataSourceChanged += new System.EventHandler(this.gridDetails_DataSourceChanged); this.gridDetails.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.gridDetails_CellFormatting); this.gridDetails.ColumnHeaderMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.gridDetails_ColumnHeaderMouseClick); + this.gridDetails.SelectionChanged += new System.EventHandler(this.gridDetails_SelectionChanged); // // LevelDetails // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(242)))), ((int)(((byte)(251))))); - this.ClientSize = new System.Drawing.Size(560, 504); + this.ClientSize = new System.Drawing.Size(614, 509); this.Controls.Add(this.gridDetails); this.DoubleBuffered = true; this.ForeColor = System.Drawing.Color.Black; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.KeyPreview = true; this.Name = "LevelDetails"; this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Level Stats"; this.Load += new System.EventHandler(this.LevelDetails_Load); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.LevelDetails_KeyDown); ((System.ComponentModel.ISupportInitialize)(this.gridDetails)).EndInit(); this.ResumeLayout(false); diff --git a/Views/LevelDetails.cs b/Views/LevelDetails.cs new file mode 100644 index 000000000..04a3713d7 --- /dev/null +++ b/Views/LevelDetails.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; +namespace FallGuysStats { + public partial class LevelDetails : Form { + public string LevelName { get; set; } + public List RoundDetails { get; set; } + public Stats StatsForm { get; set; } + private int ShowStats = 0; + public LevelDetails() { + InitializeComponent(); + } + + private void LevelDetails_Load(object sender, System.EventArgs e) { + if (LevelName == "Shows") { + Text = $"Show Stats"; + ShowStats = 2; + ClientSize = new Size(Width - 200, Height); + } else if (LevelName == "Rounds") { + Text = $"Round Stats"; + ShowStats = 1; + ClientSize = new Size(Width + 85, Height); + } else if (LevelName == "Finals") { + Text = $"Final Stats"; + ShowStats = 1; + ClientSize = new Size(Width + 85, Height); + } else { + Text = $"Level Stats - {LevelName}"; + } + + gridDetails.DataSource = RoundDetails; + } + private void gridDetails_DataSourceChanged(object sender, System.EventArgs e) { + if (gridDetails.Columns.Count == 0) { return; } + + int pos = 0; + gridDetails.Columns["Tier"].Visible = false; + gridDetails.Columns["ID"].Visible = false; + gridDetails.Columns["Crown"].Visible = false; + gridDetails.Columns["Profile"].Visible = false; + gridDetails.Columns["InParty"].Visible = false; + gridDetails.Columns["PrivateLobby"].Visible = false; + gridDetails.Columns.Add(new DataGridViewImageColumn() { Name = "Medal", ImageLayout = DataGridViewImageCellLayout.Zoom, ToolTipText = "Medal" }); + gridDetails.Setup("Medal", pos++, 24, "", DataGridViewContentAlignment.MiddleCenter); + if (ShowStats == 2) { + gridDetails.Setup("Qualified", pos++, 40, "Final", DataGridViewContentAlignment.MiddleCenter); + } else { + gridDetails.Columns["Qualified"].Visible = false; + } + gridDetails.Setup("ShowID", pos++, 0, "Show", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Round", pos++, 50, ShowStats == 2 ? "Rounds" : "Round", DataGridViewContentAlignment.MiddleRight); + if (ShowStats == 1) { + gridDetails.Setup("Name", pos++, 105, "Level", DataGridViewContentAlignment.MiddleLeft); + } else { + gridDetails.Columns["Name"].Visible = false; + } + if (ShowStats != 2) { + gridDetails.Setup("Players", pos++, 60, "Players", DataGridViewContentAlignment.MiddleRight); + } else { + gridDetails.Columns["Players"].Visible = false; + } + gridDetails.Setup("Start", pos++, 105, "Start", DataGridViewContentAlignment.MiddleCenter); + gridDetails.Setup("End", pos++, 60, "Duration", DataGridViewContentAlignment.MiddleCenter); + if (ShowStats != 2) { + gridDetails.Setup("Finish", pos++, 60, "Finish", DataGridViewContentAlignment.MiddleCenter); + } else { + gridDetails.Columns["Finish"].Visible = false; + } + if (ShowStats != 2) { + gridDetails.Setup("Position", pos++, 60, "Position", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Score", pos++, 60, "Score", DataGridViewContentAlignment.MiddleRight); + } else { + gridDetails.Columns["Position"].Visible = false; + gridDetails.Columns["Score"].Visible = false; + } + gridDetails.Setup("Kudos", pos++, 60, "Kudos", DataGridViewContentAlignment.MiddleRight); + + bool colorSwitch = true; + int lastShow = -1; + for (int i = 0; i < gridDetails.RowCount; i++) { + int showID = (int)gridDetails.Rows[i].Cells["ShowID"].Value; + if (showID != lastShow) { + colorSwitch = !colorSwitch; + lastShow = showID; + } + + if (colorSwitch) { + gridDetails.Rows[i].DefaultCellStyle.BackColor = Color.FromArgb(225, 235, 255); + } + } + } + private void gridDetails_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { + if (e.RowIndex < 0 || e.RowIndex >= gridDetails.Rows.Count) { return; } + + RoundInfo info = gridDetails.Rows[e.RowIndex].DataBoundItem as RoundInfo; + if (info.PrivateLobby) { e.CellStyle.BackColor = Color.LightGray; } + if (gridDetails.Columns[e.ColumnIndex].Name == "End") { + e.Value = (info.End - info.Start).ToString("m\\:ss"); + } else if (gridDetails.Columns[e.ColumnIndex].Name == "Start") { + e.Value = info.StartLocal.ToString("yyyy-MM-dd HH:mm"); + } else if (gridDetails.Columns[e.ColumnIndex].Name == "Finish") { + if (info.Finish.HasValue) { + e.Value = (info.Finish.Value - info.Start).ToString("m\\:ss\\.ff"); + } + } else if (ShowStats == 2 && gridDetails.Columns[e.ColumnIndex].Name == "Qualified") { + e.Value = !string.IsNullOrEmpty(info.Name); + } else if (gridDetails.Columns[e.ColumnIndex].Name == "Medal" && e.Value == null) { + if (info.Qualified) { + switch (info.Tier) { + case 0: + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = "Pink"; + e.Value = Properties.Resources.medal_pink; + break; + case 1: + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = "Gold"; + e.Value = Properties.Resources.medal_gold; + break; + case 2: + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = "Silver"; + e.Value = Properties.Resources.medal_silver; + break; + case 3: + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = "Bronze"; + e.Value = Properties.Resources.medal_bronze; + break; + } + } else { + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = "Eliminated"; + e.Value = Properties.Resources.medal_eliminated; + } + } else if (gridDetails.Columns[e.ColumnIndex].Name == "Name") { + if (StatsForm.StatLookup.TryGetValue((string)e.Value, out LevelStats level)) { + e.Value = level.Name; + } + } + } + private void gridDetails_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) { + string columnName = gridDetails.Columns[e.ColumnIndex].Name; + SortOrder sortOrder = gridDetails.GetSortOrder(columnName); + if (sortOrder == SortOrder.None) { columnName = "ShowID"; } + + RoundDetails.Sort(delegate (RoundInfo one, RoundInfo two) { + int roundCompare = one.Round.CompareTo(two.Round); + int showCompare = one.ShowID.CompareTo(two.ShowID); + if (sortOrder == SortOrder.Descending) { + RoundInfo temp = one; + one = two; + two = temp; + } + + switch (columnName) { + case "ShowID": + showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare == 0 ? roundCompare : showCompare; + case "Round": + roundCompare = one.Round.CompareTo(two.Round); + return roundCompare == 0 ? showCompare : roundCompare; + case "Name": + string nameOne = StatsForm.StatLookup.TryGetValue(one.Name, out LevelStats level1) ? level1.Name : one.Name; + string nameTwo = StatsForm.StatLookup.TryGetValue(two.Name, out LevelStats level2) ? level2.Name : two.Name; + int nameCompare = nameOne.CompareTo(nameTwo); + return nameCompare != 0 ? nameCompare : roundCompare; + case "Players": + int playerCompare = one.Players.CompareTo(two.Players); + return playerCompare != 0 ? playerCompare : showCompare == 0 ? roundCompare : showCompare; + case "Start": return one.Start.CompareTo(two.Start); + case "End": return (one.End - one.Start).CompareTo(two.End - two.Start); + case "Finish": return one.Finish.HasValue && two.Finish.HasValue ? (one.Finish.Value - one.Start).CompareTo(two.Finish.Value - two.Start) : one.Finish.HasValue ? -1 : 1; + case "Qualified": + int qualifiedCompare = ShowStats == 2 ? string.IsNullOrEmpty(one.Name).CompareTo(string.IsNullOrEmpty(two.Name)) : one.Qualified.CompareTo(two.Qualified); + return qualifiedCompare != 0 ? qualifiedCompare : showCompare == 0 ? roundCompare : showCompare; + case "Position": + int positionCompare = one.Position.CompareTo(two.Position); + return positionCompare != 0 ? positionCompare : showCompare == 0 ? roundCompare : showCompare; + case "Score": + int scoreCompare = one.Score.GetValueOrDefault(-1).CompareTo(two.Score.GetValueOrDefault(-1)); + return scoreCompare != 0 ? scoreCompare : showCompare == 0 ? roundCompare : showCompare; + case "Medal": + int tierOne = one.Qualified ? one.Tier == 0 ? 4 : one.Tier : 5; + int tierTwo = two.Qualified ? two.Tier == 0 ? 4 : two.Tier : 5; + int tierCompare = tierOne.CompareTo(tierTwo); + return tierCompare != 0 ? tierCompare : showCompare == 0 ? roundCompare : showCompare; + default: + int kudosCompare = one.Kudos.CompareTo(two.Kudos); + return kudosCompare != 0 ? kudosCompare : showCompare == 0 ? roundCompare : showCompare; + } + }); + + gridDetails.DataSource = null; + gridDetails.DataSource = RoundDetails; + gridDetails.Columns[columnName].HeaderCell.SortGlyphDirection = sortOrder; + } + private void gridDetails_SelectionChanged(object sender, EventArgs e) { + if (ShowStats != 2 && gridDetails.SelectedCells.Count > 0) { + gridDetails.ClearSelection(); + } + } + private void LevelDetails_KeyDown(object sender, KeyEventArgs e) { + try { + int selectedCount = gridDetails.SelectedCells.Count; + if (e.KeyCode == Keys.Delete && selectedCount > 0) { + HashSet rows = new HashSet(); + int minIndex = gridDetails.FirstDisplayedScrollingRowIndex; + for (int i = 0; i < selectedCount; i++) { + DataGridViewCell cell = gridDetails.SelectedCells[i]; + rows.Add((RoundInfo)gridDetails.Rows[cell.RowIndex].DataBoundItem); + } + + if (MessageBox.Show(this, $"Are you sure you want to remove the selected ({rows.Count}) Shows?", "Remove Shows", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK) { + gridDetails.DataSource = null; + + lock (StatsForm.StatsDB) { + StatsForm.StatsDB.BeginTrans(); + foreach (RoundInfo info in rows) { + RoundDetails.Remove(info); + StatsForm.RoundDetails.DeleteMany(x => x.ShowID == info.ShowID); + } + StatsForm.StatsDB.Commit(); + } + + gridDetails.DataSource = RoundDetails; + if (minIndex < RoundDetails.Count) { + gridDetails.FirstDisplayedScrollingRowIndex = minIndex; + } else if (RoundDetails.Count > 0) { + gridDetails.FirstDisplayedScrollingRowIndex = RoundDetails.Count - 1; + } + + StatsForm.ResetStats(); + } + } else if (e.KeyCode == Keys.Escape) { + Close(); + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } +} \ No newline at end of file diff --git a/LevelDetails.resx b/Views/LevelDetails.resx similarity index 100% rename from LevelDetails.resx rename to Views/LevelDetails.resx diff --git a/Views/Overlay.Designer.cs b/Views/Overlay.Designer.cs new file mode 100644 index 000000000..a95f73b1f --- /dev/null +++ b/Views/Overlay.Designer.cs @@ -0,0 +1,195 @@ +namespace FallGuysStats { + partial class Overlay { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.lblFilter = new FallGuysStats.TransparentLabel(); + this.lblStreak = new FallGuysStats.TransparentLabel(); + this.lblFinals = new FallGuysStats.TransparentLabel(); + this.lblQualifyChance = new FallGuysStats.TransparentLabel(); + this.lblFastest = new FallGuysStats.TransparentLabel(); + this.lblDuration = new FallGuysStats.TransparentLabel(); + this.lblPlayers = new FallGuysStats.TransparentLabel(); + this.lblName = new FallGuysStats.TransparentLabel(); + this.lblWins = new FallGuysStats.TransparentLabel(); + this.lblFinish = new FallGuysStats.TransparentLabel(); + this.SuspendLayout(); + // + // lblFilter + // + this.lblFilter.Location = new System.Drawing.Point(22, 77); + this.lblFilter.Name = "lblFilter"; + this.lblFilter.Size = new System.Drawing.Size(110, 22); + this.lblFilter.TabIndex = 22; + this.lblFilter.Text = "SEASON"; + this.lblFilter.TextAlign = System.Drawing.ContentAlignment.TopCenter; + this.lblFilter.TextRight = ""; + this.lblFilter.Visible = false; + // + // lblStreak + // + this.lblStreak.Location = new System.Drawing.Point(22, 55); + this.lblStreak.Name = "lblStreak"; + this.lblStreak.Size = new System.Drawing.Size(242, 22); + this.lblStreak.TabIndex = 21; + this.lblStreak.Text = "STREAK:"; + this.lblStreak.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblStreak.TextRight = "0 (BEST 0)"; + this.lblStreak.Visible = false; + // + // lblFinals + // + this.lblFinals.Location = new System.Drawing.Point(22, 32); + this.lblFinals.Name = "lblFinals"; + this.lblFinals.Size = new System.Drawing.Size(242, 22); + this.lblFinals.TabIndex = 5; + this.lblFinals.Text = "FINALS:"; + this.lblFinals.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblFinals.TextRight = "0 - 0.0%"; + this.lblFinals.Visible = false; + // + // lblQualifyChance + // + this.lblQualifyChance.Location = new System.Drawing.Point(270, 32); + this.lblQualifyChance.Name = "lblQualifyChance"; + this.lblQualifyChance.Size = new System.Drawing.Size(281, 22); + this.lblQualifyChance.TabIndex = 14; + this.lblQualifyChance.Text = "QUALIFY:"; + this.lblQualifyChance.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblQualifyChance.TextRight = "0 / 0 - 0.0%"; + this.lblQualifyChance.Visible = false; + // + // lblFastest + // + this.lblFastest.Location = new System.Drawing.Point(270, 55); + this.lblFastest.Name = "lblFastest"; + this.lblFastest.Size = new System.Drawing.Size(281, 22); + this.lblFastest.TabIndex = 16; + this.lblFastest.Text = "FASTEST:"; + this.lblFastest.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblFastest.TextRight = "-"; + this.lblFastest.Visible = false; + // + // lblDuration + // + this.lblDuration.Location = new System.Drawing.Point(557, 32); + this.lblDuration.Name = "lblDuration"; + this.lblDuration.Size = new System.Drawing.Size(225, 22); + this.lblDuration.TabIndex = 18; + this.lblDuration.Text = "TIME:"; + this.lblDuration.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblDuration.TextRight = "-"; + this.lblDuration.Visible = false; + // + // lblPlayers + // + this.lblPlayers.Location = new System.Drawing.Point(557, 9); + this.lblPlayers.Name = "lblPlayers"; + this.lblPlayers.Size = new System.Drawing.Size(225, 22); + this.lblPlayers.TabIndex = 12; + this.lblPlayers.Text = "PLAYERS:"; + this.lblPlayers.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblPlayers.TextRight = "0"; + this.lblPlayers.Visible = false; + // + // lblName + // + this.lblName.AutoEllipsis = true; + this.lblName.Location = new System.Drawing.Point(270, 9); + this.lblName.Name = "lblName"; + this.lblName.Size = new System.Drawing.Size(281, 22); + this.lblName.TabIndex = 10; + this.lblName.Text = "ROUND 1:"; + this.lblName.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblName.TextRight = "N/A"; + this.lblName.Visible = false; + // + // lblWins + // + this.lblWins.Location = new System.Drawing.Point(22, 9); + this.lblWins.Name = "lblWins"; + this.lblWins.Size = new System.Drawing.Size(242, 22); + this.lblWins.TabIndex = 1; + this.lblWins.Text = "WINS:"; + this.lblWins.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblWins.TextRight = "0 - 0.0%"; + this.lblWins.Visible = false; + // + // lblFinish + // + this.lblFinish.Location = new System.Drawing.Point(557, 55); + this.lblFinish.Name = "lblFinish"; + this.lblFinish.Size = new System.Drawing.Size(225, 22); + this.lblFinish.TabIndex = 20; + this.lblFinish.Text = "FINISH:"; + this.lblFinish.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.lblFinish.TextRight = "-"; + this.lblFinish.Visible = false; + // + // Overlay + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.BackColor = System.Drawing.Color.Magenta; + this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + this.ClientSize = new System.Drawing.Size(786, 99); + this.Controls.Add(this.lblFilter); + this.Controls.Add(this.lblStreak); + this.Controls.Add(this.lblFinals); + this.Controls.Add(this.lblQualifyChance); + this.Controls.Add(this.lblFastest); + this.Controls.Add(this.lblDuration); + this.Controls.Add(this.lblPlayers); + this.Controls.Add(this.lblName); + this.Controls.Add(this.lblWins); + this.Controls.Add(this.lblFinish); + this.Cursor = System.Windows.Forms.Cursors.SizeAll; + this.DoubleBuffered = true; + this.ForeColor = System.Drawing.Color.White; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.KeyPreview = true; + this.Name = "Overlay"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Overlay"; + this.TopMost = true; + this.TransparencyKey = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Overlay_KeyDown); + this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Overlay_MouseDown); + this.ResumeLayout(false); + + } + + #endregion + private TransparentLabel lblName; + private TransparentLabel lblDuration; + private TransparentLabel lblFinish; + private TransparentLabel lblFastest; + private TransparentLabel lblQualifyChance; + private TransparentLabel lblWins; + private TransparentLabel lblFinals; + private TransparentLabel lblPlayers; + private TransparentLabel lblStreak; + private TransparentLabel lblFilter; + } +} \ No newline at end of file diff --git a/Views/Overlay.cs b/Views/Overlay.cs new file mode 100644 index 000000000..582703e9c --- /dev/null +++ b/Views/Overlay.cs @@ -0,0 +1,718 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Drawing.Text; +using System.IO; +using System.IO.Compression; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using NewTek.NDI; +namespace FallGuysStats { + public partial class Overlay : Form { + public const int WM_NCLBUTTONDOWN = 0xA1; + public const int HT_CAPTION = 0x2; + + [DllImport("user32.dll")] + public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + [DllImport("user32.dll")] + public static extern bool ReleaseCapture(); + + public static PrivateFontCollection CustomFont; + public static Font GlobalFont; + public Stats StatsForm { get; set; } + private Thread timer; + private bool flippedImage; + private int frameCount; + private bool isTimeToSwitch; + private int switchCount; + private Bitmap Background, DrawImage; + private Graphics DrawGraphics; +#if AllowUpdate + private Sender NDISender; + private VideoFrame NDIFrame; + private Bitmap NDIImage; + private Graphics NDIGraphics; +#endif + private RoundInfo lastRound; + private int triesToDownload, drawWidth, drawHeight; + private bool startedPlaying; + private DateTime startTime; + static Overlay() { + if (!File.Exists("TitanOne-Regular.ttf")) { + using (Stream fontStream = typeof(Overlay).Assembly.GetManifestResourceStream("FallGuysStats.Resources.TitanOne-Regular.ttf")) { + byte[] fontdata = new byte[fontStream.Length]; + fontStream.Read(fontdata, 0, (int)fontStream.Length); + File.WriteAllBytes("TitanOne-Regular.ttf", fontdata); + } + } + + CustomFont = new PrivateFontCollection(); + CustomFont.AddFontFile("TitanOne-Regular.ttf"); + GlobalFont = new Font(CustomFont.Families[0], 18, FontStyle.Regular, GraphicsUnit.Pixel); + } + public void Cleanup() { +#if AllowUpdate + try { + lock (GlobalFont) { + if (NDIGraphics != null) { + NDIGraphics.Dispose(); + NDIGraphics = null; + } + if (NDIImage != null) { + NDIImage.Dispose(); + NDIImage = null; + } + if (NDIFrame != null) { + NDIFrame.Dispose(); + NDIFrame = null; + } + if (NDISender != null) { + NDISender.Dispose(); + NDISender = null; + } + } + } catch { } +#endif + } + public Overlay() { + InitializeComponent(); + + Bitmap background = Properties.Resources.background; + Bitmap newImage = new Bitmap(background.Width, background.Height, PixelFormat.Format32bppArgb); + using (Graphics g = Graphics.FromImage(newImage)) { + g.DrawImage(background, 0, 0); + } + Background = newImage; + + DrawImage = new Bitmap(background.Width, background.Height, PixelFormat.Format32bppArgb); + DrawGraphics = Graphics.FromImage(DrawImage); + drawWidth = background.Width; + drawHeight = background.Height; + + foreach (Control c in Controls) { + if (c is TransparentLabel label) { + label.Parent = this; + label.BackColor = Color.Transparent; + } + c.MouseDown += Overlay_MouseDown; + } + + SetFonts(this); + + DoubleBuffered = true; + SetStyle(ControlStyles.ResizeRedraw, true); + } + protected override void WndProc(ref Message m) { + if (m.Msg == 0x84) { + Point pos = PointToClient(new Point(m.LParam.ToInt32())); + int hitSize = 16; + if (pos.X >= ClientSize.Width - hitSize && pos.Y >= ClientSize.Height - hitSize) { + m.Result = (IntPtr)17; + return; + } else if (pos.X <= hitSize && pos.Y >= ClientSize.Height - hitSize) { + m.Result = (IntPtr)16; + return; + } else if (pos.X <= hitSize && pos.Y <= hitSize) { + m.Result = (IntPtr)13; + return; + } else if (pos.X >= ClientSize.Width - hitSize && pos.Y <= hitSize) { + m.Result = (IntPtr)14; + return; + } + } + base.WndProc(ref m); + } + public void StartTimer() { + timer = new Thread(UpdateTimer); + timer.IsBackground = true; + timer.Start(); + } + public static void SetFonts(Control control, float customSize = -1, Font font = null) { + if (font == null) { + font = customSize <= 0 ? GlobalFont : new Font(CustomFont.Families[0], customSize, FontStyle.Regular, GraphicsUnit.Pixel); + } + control.Font = font; + foreach (Control ctr in control.Controls) { + ctr.Font = font; + if (ctr.HasChildren) { + SetFonts(ctr, customSize, font); + } + } + } + private void Overlay_MouseDown(object sender, MouseEventArgs e) { + if (e.Button == MouseButtons.Left) { + ReleaseCapture(); + SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0); + } + } + private void UpdateTimer() { + while (StatsForm != null && !StatsForm.IsDisposed && !StatsForm.Disposing) { + try { + if (IsHandleCreated && !Disposing && !IsDisposed) { + frameCount++; + isTimeToSwitch = frameCount % (StatsForm.CurrentSettings.CycleTimeSeconds * 20) == 0; + Invoke((Action)UpdateInfo); + } + + StatsForm.UpdateDates(); + Thread.Sleep(50); + } catch { } + } + } + private void SetQualifyChanceLabel(StatSummary levelInfo) { + int qualifySwitchCount = switchCount; + if (!StatsForm.CurrentSettings.SwitchBetweenQualify) { + qualifySwitchCount = StatsForm.CurrentSettings.OnlyShowGold ? 1 : 0; + } + float qualifyChance; + string qualifyChanceDisplay; + switch (qualifySwitchCount % 2) { + case 0: + lblQualifyChance.Text = "QUALIFY:"; + qualifyChance = levelInfo.TotalQualify * 100f / (levelInfo.TotalPlays == 0 ? 1 : levelInfo.TotalPlays); + qualifyChanceDisplay = StatsForm.CurrentSettings.HideOverlayPercentages ? string.Empty : $" - {qualifyChance:0.0}%"; + lblQualifyChance.TextRight = $"{levelInfo.TotalQualify} / {levelInfo.TotalPlays}{qualifyChanceDisplay}"; + break; + case 1: + lblQualifyChance.Text = "GOLD:"; + qualifyChance = levelInfo.TotalGolds * 100f / (levelInfo.TotalPlays == 0 ? 1 : levelInfo.TotalPlays); + qualifyChanceDisplay = StatsForm.CurrentSettings.HideOverlayPercentages ? string.Empty : $" - {qualifyChance:0.0}%"; + lblQualifyChance.TextRight = $"{levelInfo.TotalGolds} / {levelInfo.TotalPlays}{qualifyChanceDisplay}"; + break; + } + } + private void SetFastestLabel(StatSummary levelInfo, LevelType type) { + int fastestSwitchCount = switchCount; + if (!StatsForm.CurrentSettings.SwitchBetweenLongest) { + fastestSwitchCount = StatsForm.CurrentSettings.OnlyShowLongest ? 0 : type.FastestLabel(); + } + switch (fastestSwitchCount % (levelInfo.BestScore.HasValue ? 3 : 2)) { + case 0: + lblFastest.Text = "LONGEST:"; + lblFastest.TextRight = levelInfo.LongestFinish.HasValue ? $"{levelInfo.LongestFinish:m\\:ss\\.ff}" : "-"; + break; + case 1: + lblFastest.Text = "FASTEST:"; + lblFastest.TextRight = levelInfo.BestFinish.HasValue ? $"{levelInfo.BestFinish:m\\:ss\\.ff}" : "-"; + break; + case 2: + lblFastest.Text = "HIGH SCORE:"; + lblFastest.TextRight = levelInfo.BestScore.Value.ToString(); + break; + } + } + private void SetPlayersLabel() { + int playersSwitchCount = switchCount; + if (!StatsForm.CurrentSettings.SwitchBetweenPlayers) { + playersSwitchCount = StatsForm.CurrentSettings.OnlyShowPing ? 1 : 0; + } + switch (playersSwitchCount % 2) { + case 0: + lblPlayers.Text = "PLAYERS:"; + lblPlayers.TextRight = lastRound?.Players.ToString(); + break; + case 1: + lblPlayers.Text = "PING:"; + lblPlayers.TextRight = Stats.InShow && Stats.LastServerPing != 0 ? $"{Stats.LastServerPing} ms" : "-"; + break; + } + } + private void SetStreakInfo(StatSummary levelInfo) { + int streakSwitchCount = switchCount; + if (!StatsForm.CurrentSettings.SwitchBetweenStreaks) { + streakSwitchCount = StatsForm.CurrentSettings.OnlyShowFinalStreak ? 1 : 0; + } + switch (streakSwitchCount % 2) { + case 0: + lblStreak.Text = "WIN STREAK:"; + lblStreak.TextRight = $"{levelInfo.CurrentStreak} ({levelInfo.BestStreak})"; + break; + case 1: + lblStreak.Text = "FINAL STREAK:"; + lblStreak.TextRight = $"{levelInfo.CurrentFinalStreak} ({levelInfo.BestFinalStreak})"; + break; + } + } + private void UpdateInfo() { + if (StatsForm == null) { return; } + + lock (StatsForm.CurrentRound) { + bool hasCurrentRound = StatsForm.CurrentRound != null && StatsForm.CurrentRound.Count > 0; + if (hasCurrentRound && (lastRound == null || Stats.InShow || Stats.EndedShow)) { + if (Stats.EndedShow) { + Stats.EndedShow = false; + } + lastRound = StatsForm.CurrentRound[StatsForm.CurrentRound.Count - 1]; + } + + lblFilter.Text = StatsForm.GetCurrentFilter(); + + if (lastRound != null && !string.IsNullOrEmpty(lastRound.Name)) { + string roundName = lastRound.VerifiedName(); + lblName.Text = $"ROUND {lastRound.Round}:"; + + if (StatsForm.StatLookup.TryGetValue(roundName, out var level)) { + roundName = level.Name.ToUpper(); + } + + if (roundName.StartsWith("round_", StringComparison.OrdinalIgnoreCase)) { + roundName = roundName.Substring(6).Replace('_', ' ').ToUpper(); + } + if (roundName.Length > 15) { roundName = roundName.Substring(0, 15); } + + StatSummary levelInfo = StatsForm.GetLevelInfo(roundName); + lblName.TextRight = roundName; + + float winChance = levelInfo.TotalWins * 100f / (levelInfo.TotalShows == 0 ? 1 : levelInfo.TotalShows); + string winChanceDisplay = StatsForm.CurrentSettings.HideOverlayPercentages ? string.Empty : $" - {winChance:0.0}%"; + if (StatsForm.CurrentSettings.PreviousWins > 0) { + lblWins.TextRight = $"{levelInfo.TotalWins} ({levelInfo.AllWins + StatsForm.CurrentSettings.PreviousWins}){winChanceDisplay}"; + } else if (StatsForm.CurrentSettings.FilterType != 0) { + lblWins.TextRight = $"{levelInfo.TotalWins} ({levelInfo.AllWins}){winChanceDisplay}"; + } else { + lblWins.TextRight = $"{levelInfo.TotalWins}{winChanceDisplay}"; + } + + float finalChance = levelInfo.TotalFinals * 100f / (levelInfo.TotalShows == 0 ? 1 : levelInfo.TotalShows); + string finalText = $"{levelInfo.TotalFinals} / {levelInfo.TotalShows}"; + string finalChanceDisplay = StatsForm.CurrentSettings.HideOverlayPercentages ? string.Empty : finalText.Length > 9 ? $" - {finalChance:0}%" : $" - {finalChance:0.0}%"; + lblFinals.TextRight = $"{finalText}{finalChanceDisplay}"; + + SetQualifyChanceLabel(levelInfo); + LevelType levelType = (level?.Type).GetValueOrDefault(); + SetFastestLabel(levelInfo, levelType); + SetPlayersLabel(); + SetStreakInfo(levelInfo); + if (isTimeToSwitch) { + switchCount++; + } + + DateTime Start = lastRound.Start; + DateTime End = lastRound.End; + DateTime? Finish = lastRound.Finish; + + if (lastRound.Playing != startedPlaying) { + if (lastRound.Playing) { + startTime = DateTime.UtcNow; + } + startedPlaying = lastRound.Playing; + } + + if (Finish.HasValue) { + TimeSpan Time = Finish.GetValueOrDefault(End) - Start; + if (lastRound.Position > 0) { + lblFinish.TextRight = $"# {lastRound.Position} - {Time:m\\:ss\\.ff}"; + } else { + lblFinish.TextRight = $"{Time:m\\:ss\\.ff}"; + } + + if (levelType == LevelType.Race || levelType == LevelType.Hunt || roundName == "ROCK'N'ROLL" || roundName == "SNOWY SCRAP") { + if (Time < levelInfo.BestFinish.GetValueOrDefault(TimeSpan.MaxValue) && Time > levelInfo.BestFinishOverall.GetValueOrDefault(TimeSpan.MaxValue)) { + lblFinish.ForeColor = Color.LightGreen; + } else if (Time < levelInfo.BestFinishOverall.GetValueOrDefault(TimeSpan.MaxValue)) { + lblFinish.ForeColor = Color.Gold; + } + } else if (Time > levelInfo.LongestFinish && Time < levelInfo.LongestFinishOverall) { + lblFinish.ForeColor = Color.LightGreen; + } else if (Time > levelInfo.LongestFinishOverall) { + lblFinish.ForeColor = Color.Gold; + } + } else if (lastRound.Playing) { + if (Start > DateTime.UtcNow) { + lblFinish.TextRight = $"{DateTime.UtcNow - startTime:m\\:ss}"; + } else { + lblFinish.TextRight = $"{DateTime.UtcNow - Start:m\\:ss}"; + } + } else { + lblFinish.TextRight = "-"; + lblFinish.ForeColor = Color.White; + } + + if (lastRound.GameDuration > 0) { + lblDuration.Text = $"TIME ({TimeSpan.FromSeconds(lastRound.GameDuration):m\\:ss}):"; + } else { + lblDuration.Text = "TIME:"; + } + + if (End != DateTime.MinValue) { + lblDuration.TextRight = $"{End - Start:m\\:ss\\.ff}"; + } else if (lastRound.Playing) { + if (Start > DateTime.UtcNow) { + lblDuration.TextRight = $"{DateTime.UtcNow - startTime:m\\:ss}"; + } else { + lblDuration.TextRight = $"{DateTime.UtcNow - Start:m\\:ss}"; + } + } else { + lblDuration.TextRight = "-"; + } + } + Invalidate(); + } + + if (StatsForm.CurrentSettings.UseNDI) { + SendNDI(); + } + } + private void SendNDI() { +#if AllowUpdate + try { + lock (GlobalFont) { + if (NDISender == null) { + NDISender = new Sender("Fall Guys Stats Overlay", true, false, null, "Fall Guys Stats Overlay"); + } + if (NDIFrame == null) { + NDIFrame = new VideoFrame(Background.Width, Background.Height, (float)Background.Width / Background.Height, 20, 1); + } + if (NDIImage == null) { + NDIImage = new Bitmap(NDIFrame.Width, NDIFrame.Height, NDIFrame.Stride, PixelFormat.Format32bppPArgb, NDIFrame.BufferPtr); + } + if (NDIGraphics == null) { + NDIGraphics = Graphics.FromImage(NDIImage); + NDIGraphics.SmoothingMode = SmoothingMode.AntiAlias; + } + + NDIGraphics.Clear(Color.Transparent); + NDIGraphics.DrawImage(Background, 0, 0); + + foreach (Control control in Controls) { + if (control is TransparentLabel label) { + NDIGraphics.TranslateTransform(label.Location.X, label.Location.Y); + label.Draw(NDIGraphics); + NDIGraphics.TranslateTransform(-label.Location.X, -label.Location.Y); + } + } + + NDISender.Send(NDIFrame); + } + } catch (Exception ex) { + if (ex.Message.IndexOf("Unable to load dll", StringComparison.OrdinalIgnoreCase) >= 0) { + StatsForm.CurrentSettings.UseNDI = false; + Cleanup(); + if (triesToDownload < 5) { + triesToDownload++; + Task.Run(delegate () { + try { + using (ZipWebClient web = new ZipWebClient()) { + byte[] data = web.DownloadData($"https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/NDI.zip"); + using (MemoryStream ms = new MemoryStream(data)) { + using (ZipArchive zipFile = new ZipArchive(ms, ZipArchiveMode.Read)) { + foreach (var entry in zipFile.Entries) { + entry.ExtractToFile(entry.Name, true); + } + } + } + } + StatsForm.CurrentSettings.UseNDI = true; + } catch { + Thread.Sleep(10000); + } + }); + } + } + } +#endif + } + protected override void OnPaint(PaintEventArgs e) { + lock (GlobalFont) { + DrawGraphics.Clear(Color.Transparent); + DrawGraphics.DrawImage(Background, 0, 0); + + foreach (Control control in Controls) { + if (control is TransparentLabel label) { + DrawGraphics.TranslateTransform(label.Location.X, label.Location.Y); + label.Draw(DrawGraphics); + DrawGraphics.TranslateTransform(-label.Location.X, -label.Location.Y); + } + } + + e.Graphics.InterpolationMode = InterpolationMode.Default; + e.Graphics.PixelOffsetMode = PixelOffsetMode.None; + e.Graphics.SmoothingMode = SmoothingMode.None; + e.Graphics.DrawImage(DrawImage, new Rectangle(0, 0, ClientSize.Width, ClientSize.Height), new Rectangle(0, 0, DrawImage.Width, DrawImage.Height), GraphicsUnit.Pixel); + } + } + private void Overlay_KeyDown(object sender, KeyEventArgs e) { + if (e.KeyCode == Keys.T) { + int colorOption = 0; + if (BackColor.ToArgb() == Color.Black.ToArgb()) { + colorOption = 5; + } else if (BackColor.ToArgb() == Color.Green.ToArgb()) { + colorOption = 0; + } else if (BackColor.ToArgb() == Color.Magenta.ToArgb()) { + colorOption = 1; + } else if (BackColor.ToArgb() == Color.Blue.ToArgb()) { + colorOption = 2; + } else if (BackColor.ToArgb() == Color.Red.ToArgb()) { + colorOption = 3; + } else if (BackColor.ToArgb() == Color.FromArgb(224, 224, 224).ToArgb()) { + colorOption = 4; + } + SetBackgroundColor(colorOption); + + StatsForm.CurrentSettings.OverlayColor = colorOption; + StatsForm.SaveUserSettings(); + } else if (e.KeyCode == Keys.F) { + FlipDisplay(!flippedImage); + Background = RecreateBackground(); + + StatsForm.CurrentSettings.FlippedDisplay = flippedImage; + StatsForm.SaveUserSettings(); + } + } + public void SetBackgroundColor(int colorOption) { + switch (colorOption) { + case 0: BackColor = Color.Magenta; break; + case 1: BackColor = Color.Blue; break; + case 2: BackColor = Color.Red; break; + case 3: BackColor = Color.FromArgb(224, 224, 224); break; + case 4: BackColor = Color.Black; break; + case 5: BackColor = Color.Green; break; + } + } + public void ArrangeDisplay(bool flipDisplay, bool showTabs, bool hideWins, bool hideRound, bool hideTime, int colorOption, int? width, int? height, string serializedFont) { + FlipDisplay(false); + + int heightOffset = showTabs ? 35 : 0; + lblWins.Location = new Point(22, 9 + heightOffset); + lblFinals.Location = new Point(22, 32 + heightOffset); + lblStreak.Location = new Point(22, 55 + heightOffset); + + int spacerWidth = 6; + int firstColumnX = 22; + int firstColumnWidth = 242; + int secondColumnX = firstColumnX + firstColumnWidth + spacerWidth; + int secondColumnWidth = 281; + int thirdColumnX = secondColumnX + secondColumnWidth + spacerWidth; + int thirdColumnWidth = 225; + + lblWins.Size = new Size(firstColumnWidth, 22); + lblFinals.Size = new Size(firstColumnWidth, 22); + lblStreak.Size = new Size(firstColumnWidth, 22); + + drawWidth = firstColumnWidth + secondColumnWidth + thirdColumnWidth + spacerWidth * 3 + firstColumnX - 2; + + int overlaySetting = (hideWins ? 4 : 0) + (hideRound ? 2 : 0) + (hideTime ? 1 : 0); + switch (overlaySetting) { + case 0: + lblWins.Location = new Point(firstColumnX, 9 + heightOffset); + lblWins.DrawVisible = true; + lblFinals.Location = new Point(firstColumnX, 32 + heightOffset); + lblFinals.DrawVisible = true; + lblStreak.Location = new Point(firstColumnX, 55 + heightOffset); + lblStreak.DrawVisible = true; + + lblName.Location = new Point(secondColumnX, 9 + heightOffset); + lblName.DrawVisible = true; + lblQualifyChance.Location = new Point(secondColumnX, 32 + heightOffset); + lblQualifyChance.DrawVisible = true; + lblFastest.Location = new Point(secondColumnX, 55 + heightOffset); + lblFastest.Size = new Size(secondColumnWidth, 22); + lblFastest.DrawVisible = true; + + lblPlayers.Location = new Point(thirdColumnX, 9 + heightOffset); + lblPlayers.Size = new Size(thirdColumnWidth, 22); + lblPlayers.DrawVisible = true; + lblDuration.Location = new Point(thirdColumnX, 32 + heightOffset); + lblDuration.DrawVisible = true; + lblFinish.Location = new Point(thirdColumnX, 55 + heightOffset); + lblFinish.DrawVisible = true; + break; + case 1: + drawWidth -= thirdColumnWidth + spacerWidth; + + lblWins.Location = new Point(firstColumnX, 9 + heightOffset); + lblWins.DrawVisible = true; + lblFinals.Location = new Point(firstColumnX, 32 + heightOffset); + lblFinals.DrawVisible = true; + lblStreak.Location = new Point(firstColumnX, 55 + heightOffset); + lblStreak.DrawVisible = true; + + lblFastest.DrawVisible = false; + lblDuration.DrawVisible = false; + lblFinish.DrawVisible = false; + + lblName.Location = new Point(secondColumnX, 9 + heightOffset); + lblName.DrawVisible = true; + lblPlayers.Location = new Point(secondColumnX, 32 + heightOffset); + lblPlayers.Size = new Size(secondColumnWidth, 22); + lblPlayers.DrawVisible = true; + lblQualifyChance.Location = new Point(secondColumnX, 55 + heightOffset); + lblQualifyChance.DrawVisible = true; + break; + case 2: + drawWidth -= secondColumnWidth + spacerWidth; + + lblWins.Location = new Point(firstColumnX, 9 + heightOffset); + lblWins.DrawVisible = true; + lblFinals.Location = new Point(firstColumnX, 32 + heightOffset); + lblFinals.DrawVisible = true; + lblStreak.Location = new Point(firstColumnX, 55 + heightOffset); + lblStreak.DrawVisible = true; + + lblName.DrawVisible = false; + lblQualifyChance.DrawVisible = false; + lblPlayers.DrawVisible = false; + + lblFastest.Location = new Point(secondColumnX, 9 + heightOffset); + lblFastest.Size = new Size(thirdColumnWidth, 22); + lblFastest.DrawVisible = true; + lblDuration.Location = new Point(secondColumnX, 32 + heightOffset); + lblDuration.DrawVisible = true; + lblFinish.Location = new Point(secondColumnX, 55 + heightOffset); + lblFinish.DrawVisible = true; + break; + case 3: + drawWidth -= secondColumnWidth + thirdColumnWidth + spacerWidth * 2; + + lblWins.Location = new Point(firstColumnX, 9 + heightOffset); + lblWins.DrawVisible = true; + lblFinals.Location = new Point(firstColumnX, 32 + heightOffset); + lblFinals.DrawVisible = true; + lblStreak.Location = new Point(firstColumnX, 55 + heightOffset); + lblStreak.DrawVisible = true; + + lblName.DrawVisible = false; + lblQualifyChance.DrawVisible = false; + lblPlayers.DrawVisible = false; + + lblFastest.DrawVisible = false; + lblDuration.DrawVisible = false; + lblFinish.DrawVisible = false; + break; + case 4: + drawWidth -= firstColumnWidth + spacerWidth; + + lblWins.DrawVisible = false; + lblFinals.DrawVisible = false; + lblStreak.DrawVisible = false; + + lblName.Location = new Point(firstColumnX, 9 + heightOffset); + lblName.DrawVisible = true; + lblQualifyChance.Location = new Point(firstColumnX, 32 + heightOffset); + lblQualifyChance.DrawVisible = true; + lblFastest.Location = new Point(firstColumnX, 55 + heightOffset); + lblFastest.Size = new Size(secondColumnWidth, 22); + lblFastest.DrawVisible = true; + + lblPlayers.Location = new Point(firstColumnX + secondColumnWidth + 6, 9 + heightOffset); + lblPlayers.Size = new Size(thirdColumnWidth, 22); + lblPlayers.DrawVisible = true; + lblDuration.Location = new Point(firstColumnX + secondColumnWidth + 6, 32 + heightOffset); + lblDuration.DrawVisible = true; + lblFinish.Location = new Point(firstColumnX + secondColumnWidth + 6, 55 + heightOffset); + lblFinish.DrawVisible = true; + break; + case 5: + drawWidth -= firstColumnWidth + thirdColumnWidth + spacerWidth * 2; + + lblWins.DrawVisible = false; + lblFinals.DrawVisible = false; + lblStreak.DrawVisible = false; + + lblName.Location = new Point(firstColumnX, 9 + heightOffset); + lblName.DrawVisible = true; + lblPlayers.Location = new Point(firstColumnX, 32 + heightOffset); + lblPlayers.Size = new Size(secondColumnWidth, 22); + lblPlayers.DrawVisible = true; + lblQualifyChance.Location = new Point(firstColumnX, 55 + heightOffset); + lblQualifyChance.DrawVisible = true; + + lblFastest.DrawVisible = false; + lblDuration.DrawVisible = false; + lblFinish.DrawVisible = false; + break; + case 6: + drawWidth -= firstColumnWidth + secondColumnWidth + spacerWidth * 2; + + lblWins.DrawVisible = false; + lblFinals.DrawVisible = false; + lblStreak.DrawVisible = false; + + lblName.DrawVisible = false; + lblQualifyChance.DrawVisible = false; + lblPlayers.DrawVisible = false; + + lblFastest.Location = new Point(firstColumnX, 9 + heightOffset); + lblFastest.Size = new Size(thirdColumnWidth, 22); + lblFastest.DrawVisible = true; + lblDuration.Location = new Point(firstColumnX, 32 + heightOffset); + lblDuration.DrawVisible = true; + lblFinish.Location = new Point(firstColumnX, 55 + heightOffset); + lblFinish.DrawVisible = true; + break; + } + + if (!string.IsNullOrEmpty(serializedFont)) { + FontConverter fontConverter = new FontConverter(); + SetFonts(this, -1, fontConverter.ConvertFromString(serializedFont) as Font); + } else { + SetFonts(this); + } + + DisplayTabs(showTabs); + FlipDisplay(flipDisplay); + SetBackgroundColor(colorOption); + Cleanup(); + + Background = RecreateBackground(); + if (width.HasValue) { + Width = width.Value; + } + if (height.HasValue) { + Height = height.Value; + } + } + public void FlipDisplay(bool flipped) { + if (flipped == flippedImage) { return; } + flippedImage = flipped; + + foreach (Control ctr in Controls) { + if (ctr is TransparentLabel label && label != lblFilter) { + label.Location = new Point(label.Location.X + (flippedImage ? -18 : 18), label.Location.Y); + } + } + + DisplayTabs(drawHeight > 99); + } + private Bitmap RecreateBackground() { + lock (GlobalFont) { + if (Background != null) { + Background.Dispose(); + } + + bool tabsDisplayed = StatsForm.CurrentSettings.ShowOverlayTabs; + Bitmap newImage = new Bitmap(drawWidth, drawHeight, PixelFormat.Format32bppArgb); + using (Graphics g = Graphics.FromImage(newImage)) { + g.DrawImage(Properties.Resources.background, 0, tabsDisplayed ? 35 : 0); + if (tabsDisplayed) { + g.DrawImage(Properties.Resources.tab_unselected, drawWidth - 110, 0); + } + } + + if (flippedImage) { + newImage.RotateFlip(RotateFlipType.RotateNoneFlipX); + } + + DrawGraphics.Dispose(); + DrawImage.Dispose(); + DrawImage = new Bitmap(newImage.Width, newImage.Height, PixelFormat.Format32bppArgb); + DrawGraphics = Graphics.FromImage(DrawImage); + + return newImage; + } + } + public void DisplayTabs(bool showTabs) { + if (showTabs) { + drawHeight = 134; + lblFilter.Location = new Point(flippedImage ? -5 : drawWidth - 105, 11); + lblFilter.DrawVisible = true; + } else { + drawHeight = 99; + lblFilter.DrawVisible = false; + } + } + } +} \ No newline at end of file diff --git a/Views/Overlay.resx b/Views/Overlay.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/Views/Overlay.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Views/Settings.Designer.cs b/Views/Settings.Designer.cs new file mode 100644 index 000000000..c38fedf0a --- /dev/null +++ b/Views/Settings.Designer.cs @@ -0,0 +1,975 @@ +namespace FallGuysStats { + partial class Settings { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.lblLogPath = new System.Windows.Forms.Label(); + this.lblLogPathNote = new System.Windows.Forms.Label(); + this.txtLogPath = new System.Windows.Forms.TextBox(); + this.btnSave = new System.Windows.Forms.Button(); + this.grpOverlay = new System.Windows.Forms.GroupBox(); + this.grpCycleQualifyGold = new System.Windows.Forms.GroupBox(); + this.chkOnlyShowGold = new System.Windows.Forms.RadioButton(); + this.chkOnlyShowQualify = new System.Windows.Forms.RadioButton(); + this.chkCycleQualifyGold = new System.Windows.Forms.RadioButton(); + this.grpCycleFastestLongest = new System.Windows.Forms.GroupBox(); + this.chkOnlyShowLongest = new System.Windows.Forms.RadioButton(); + this.chkOnlyShowFastest = new System.Windows.Forms.RadioButton(); + this.chkCycleFastestLongest = new System.Windows.Forms.RadioButton(); + this.chkHidePercentages = new System.Windows.Forms.CheckBox(); + this.chkHideWinsInfo = new System.Windows.Forms.CheckBox(); + this.cboOverlayColor = new System.Windows.Forms.ComboBox(); + this.lblOverlayColor = new System.Windows.Forms.Label(); + this.chkFlipped = new System.Windows.Forms.CheckBox(); + this.chkShowTabs = new System.Windows.Forms.CheckBox(); + this.chkHideTimeInfo = new System.Windows.Forms.CheckBox(); + this.chkHideRoundInfo = new System.Windows.Forms.CheckBox(); + this.cboFastestFilter = new System.Windows.Forms.ComboBox(); + this.lblFastestFilter = new System.Windows.Forms.Label(); + this.cboQualifyFilter = new System.Windows.Forms.ComboBox(); + this.lblQualifyFilter = new System.Windows.Forms.Label(); + this.cboWinsFilter = new System.Windows.Forms.ComboBox(); + this.lblWinsFilter = new System.Windows.Forms.Label(); + this.chkOverlayOnTop = new System.Windows.Forms.CheckBox(); + this.chkUseNDI = new System.Windows.Forms.CheckBox(); + this.lblCycleTimeSecondsTag = new System.Windows.Forms.Label(); + this.lblCycleTimeSeconds = new System.Windows.Forms.Label(); + this.txtCycleTimeSeconds = new System.Windows.Forms.TextBox(); + this.grpCycleWinFinalStreak = new System.Windows.Forms.GroupBox(); + this.chkOnlyShowFinalStreak = new System.Windows.Forms.RadioButton(); + this.chkOnlyShowWinStreak = new System.Windows.Forms.RadioButton(); + this.chkCycleWinFinalStreak = new System.Windows.Forms.RadioButton(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.chkOnlyShowPing = new System.Windows.Forms.RadioButton(); + this.chkOnlyShowPlayers = new System.Windows.Forms.RadioButton(); + this.chkCyclePlayersPing = new System.Windows.Forms.RadioButton(); + this.lblOverlayFont = new System.Windows.Forms.Label(); + this.btnSelectFont = new System.Windows.Forms.Button(); + this.btnResetOverlayFont = new System.Windows.Forms.Button(); + this.grpOverlayFontExample = new System.Windows.Forms.GroupBox(); + this.lblOverlayFontExample = new System.Windows.Forms.Label(); + this.grpStats = new System.Windows.Forms.GroupBox(); + this.chkChangeHoopsieLegends = new System.Windows.Forms.CheckBox(); + this.chkAutoUpdate = new System.Windows.Forms.CheckBox(); + this.lblPreviousWinsNote = new System.Windows.Forms.Label(); + this.lblPreviousWins = new System.Windows.Forms.Label(); + this.txtPreviousWins = new System.Windows.Forms.TextBox(); + this.grpGameOptions = new System.Windows.Forms.GroupBox(); + this.lblGameExeLocation = new System.Windows.Forms.Label(); + this.txtGameExeLocation = new System.Windows.Forms.TextBox(); + this.btnGameExeLocationBrowse = new System.Windows.Forms.Button(); + this.chkAutoLaunchGameOnStart = new System.Windows.Forms.CheckBox(); + this.grpSortingOptions = new System.Windows.Forms.GroupBox(); + this.chkIgnoreLevelTypeWhenSorting = new System.Windows.Forms.CheckBox(); + this.btnCancel = new System.Windows.Forms.Button(); + this.dlgOverlayFont = new System.Windows.Forms.FontDialog(); + this.trkOverlayScale = new System.Windows.Forms.TrackBar(); + this.lblOverlayScaling = new System.Windows.Forms.Label(); + this.lblOverlayScale = new System.Windows.Forms.Label(); + this.btnOverlayScaleReset = new System.Windows.Forms.Button(); + this.grpOverlay.SuspendLayout(); + this.grpCycleQualifyGold.SuspendLayout(); + this.grpCycleFastestLongest.SuspendLayout(); + this.grpCycleWinFinalStreak.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.grpOverlayFontExample.SuspendLayout(); + this.grpStats.SuspendLayout(); + this.grpGameOptions.SuspendLayout(); + this.grpSortingOptions.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trkOverlayScale)).BeginInit(); + this.SuspendLayout(); + // + // lblLogPath + // + this.lblLogPath.AutoSize = true; + this.lblLogPath.Location = new System.Drawing.Point(12, 23); + this.lblLogPath.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblLogPath.Name = "lblLogPath"; + this.lblLogPath.Size = new System.Drawing.Size(73, 20); + this.lblLogPath.TabIndex = 0; + this.lblLogPath.Text = "Log Path"; + // + // lblLogPathNote + // + this.lblLogPathNote.AutoSize = true; + this.lblLogPathNote.ForeColor = System.Drawing.Color.DimGray; + this.lblLogPathNote.Location = new System.Drawing.Point(92, 54); + this.lblLogPathNote.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblLogPathNote.Name = "lblLogPathNote"; + this.lblLogPathNote.Size = new System.Drawing.Size(682, 20); + this.lblLogPathNote.TabIndex = 2; + this.lblLogPathNote.Text = "* You should not need to set this. Only use when the program is not reading the c" + + "orrect location."; + // + // txtLogPath + // + this.txtLogPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtLogPath.Location = new System.Drawing.Point(96, 18); + this.txtLogPath.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.txtLogPath.Name = "txtLogPath"; + this.txtLogPath.Size = new System.Drawing.Size(888, 26); + this.txtLogPath.TabIndex = 1; + this.txtLogPath.Validating += new System.ComponentModel.CancelEventHandler(this.txtLogPath_Validating); + // + // btnSave + // + this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnSave.Location = new System.Drawing.Point(750, 917); + this.btnSave.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(112, 35); + this.btnSave.TabIndex = 7; + this.btnSave.Text = "Save"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // grpOverlay + // + this.grpOverlay.Controls.Add(this.btnOverlayScaleReset); + this.grpOverlay.Controls.Add(this.lblOverlayScale); + this.grpOverlay.Controls.Add(this.lblOverlayScaling); + this.grpOverlay.Controls.Add(this.trkOverlayScale); + this.grpOverlay.Controls.Add(this.grpCycleQualifyGold); + this.grpOverlay.Controls.Add(this.grpCycleFastestLongest); + this.grpOverlay.Controls.Add(this.chkHidePercentages); + this.grpOverlay.Controls.Add(this.chkHideWinsInfo); + this.grpOverlay.Controls.Add(this.cboOverlayColor); + this.grpOverlay.Controls.Add(this.lblOverlayColor); + this.grpOverlay.Controls.Add(this.chkFlipped); + this.grpOverlay.Controls.Add(this.chkShowTabs); + this.grpOverlay.Controls.Add(this.chkHideTimeInfo); + this.grpOverlay.Controls.Add(this.chkHideRoundInfo); + this.grpOverlay.Controls.Add(this.cboFastestFilter); + this.grpOverlay.Controls.Add(this.lblFastestFilter); + this.grpOverlay.Controls.Add(this.cboQualifyFilter); + this.grpOverlay.Controls.Add(this.lblQualifyFilter); + this.grpOverlay.Controls.Add(this.cboWinsFilter); + this.grpOverlay.Controls.Add(this.lblWinsFilter); + this.grpOverlay.Controls.Add(this.chkOverlayOnTop); + this.grpOverlay.Controls.Add(this.chkUseNDI); + this.grpOverlay.Controls.Add(this.lblCycleTimeSecondsTag); + this.grpOverlay.Controls.Add(this.lblCycleTimeSeconds); + this.grpOverlay.Controls.Add(this.txtCycleTimeSeconds); + this.grpOverlay.Controls.Add(this.grpCycleWinFinalStreak); + this.grpOverlay.Controls.Add(this.groupBox1); + this.grpOverlay.Controls.Add(this.lblOverlayFont); + this.grpOverlay.Controls.Add(this.btnSelectFont); + this.grpOverlay.Controls.Add(this.btnResetOverlayFont); + this.grpOverlay.Controls.Add(this.grpOverlayFontExample); + this.grpOverlay.Location = new System.Drawing.Point(18, 177); + this.grpOverlay.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.grpOverlay.Name = "grpOverlay"; + this.grpOverlay.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.grpOverlay.Size = new System.Drawing.Size(968, 542); + this.grpOverlay.TabIndex = 4; + this.grpOverlay.TabStop = false; + this.grpOverlay.Text = "Overlay"; + // + // grpCycleQualifyGold + // + this.grpCycleQualifyGold.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.grpCycleQualifyGold.Controls.Add(this.chkOnlyShowGold); + this.grpCycleQualifyGold.Controls.Add(this.chkOnlyShowQualify); + this.grpCycleQualifyGold.Controls.Add(this.chkCycleQualifyGold); + this.grpCycleQualifyGold.Location = new System.Drawing.Point(18, 263); + this.grpCycleQualifyGold.Margin = new System.Windows.Forms.Padding(0); + this.grpCycleQualifyGold.Name = "grpCycleQualifyGold"; + this.grpCycleQualifyGold.Size = new System.Drawing.Size(554, 49); + this.grpCycleQualifyGold.TabIndex = 8; + this.grpCycleQualifyGold.TabStop = false; + // + // chkOnlyShowGold + // + this.chkOnlyShowGold.AutoSize = true; + this.chkOnlyShowGold.Location = new System.Drawing.Point(388, 15); + this.chkOnlyShowGold.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowGold.Name = "chkOnlyShowGold"; + this.chkOnlyShowGold.Size = new System.Drawing.Size(103, 24); + this.chkOnlyShowGold.TabIndex = 2; + this.chkOnlyShowGold.Text = "Gold Only"; + this.chkOnlyShowGold.UseVisualStyleBackColor = true; + // + // chkOnlyShowQualify + // + this.chkOnlyShowQualify.AutoSize = true; + this.chkOnlyShowQualify.Location = new System.Drawing.Point(226, 15); + this.chkOnlyShowQualify.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowQualify.Name = "chkOnlyShowQualify"; + this.chkOnlyShowQualify.Size = new System.Drawing.Size(117, 24); + this.chkOnlyShowQualify.TabIndex = 1; + this.chkOnlyShowQualify.Text = "Qualify Only"; + this.chkOnlyShowQualify.UseVisualStyleBackColor = true; + // + // chkCycleQualifyGold + // + this.chkCycleQualifyGold.AutoSize = true; + this.chkCycleQualifyGold.Checked = true; + this.chkCycleQualifyGold.Location = new System.Drawing.Point(8, 15); + this.chkCycleQualifyGold.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkCycleQualifyGold.Name = "chkCycleQualifyGold"; + this.chkCycleQualifyGold.Size = new System.Drawing.Size(170, 24); + this.chkCycleQualifyGold.TabIndex = 0; + this.chkCycleQualifyGold.TabStop = true; + this.chkCycleQualifyGold.Text = "Cycle Qualify / Gold"; + this.chkCycleQualifyGold.UseVisualStyleBackColor = true; + // + // grpCycleFastestLongest + // + this.grpCycleFastestLongest.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.grpCycleFastestLongest.Controls.Add(this.chkOnlyShowLongest); + this.grpCycleFastestLongest.Controls.Add(this.chkOnlyShowFastest); + this.grpCycleFastestLongest.Controls.Add(this.chkCycleFastestLongest); + this.grpCycleFastestLongest.Location = new System.Drawing.Point(18, 302); + this.grpCycleFastestLongest.Margin = new System.Windows.Forms.Padding(0); + this.grpCycleFastestLongest.Name = "grpCycleFastestLongest"; + this.grpCycleFastestLongest.Size = new System.Drawing.Size(554, 49); + this.grpCycleFastestLongest.TabIndex = 9; + this.grpCycleFastestLongest.TabStop = false; + // + // chkOnlyShowLongest + // + this.chkOnlyShowLongest.AutoSize = true; + this.chkOnlyShowLongest.Location = new System.Drawing.Point(388, 15); + this.chkOnlyShowLongest.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowLongest.Name = "chkOnlyShowLongest"; + this.chkOnlyShowLongest.Size = new System.Drawing.Size(127, 24); + this.chkOnlyShowLongest.TabIndex = 2; + this.chkOnlyShowLongest.Text = "Longest Only"; + this.chkOnlyShowLongest.UseVisualStyleBackColor = true; + // + // chkOnlyShowFastest + // + this.chkOnlyShowFastest.AutoSize = true; + this.chkOnlyShowFastest.Location = new System.Drawing.Point(226, 15); + this.chkOnlyShowFastest.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowFastest.Name = "chkOnlyShowFastest"; + this.chkOnlyShowFastest.Size = new System.Drawing.Size(123, 24); + this.chkOnlyShowFastest.TabIndex = 1; + this.chkOnlyShowFastest.Text = "Fastest Only"; + this.chkOnlyShowFastest.UseVisualStyleBackColor = true; + // + // chkCycleFastestLongest + // + this.chkCycleFastestLongest.AutoSize = true; + this.chkCycleFastestLongest.Checked = true; + this.chkCycleFastestLongest.Location = new System.Drawing.Point(8, 15); + this.chkCycleFastestLongest.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkCycleFastestLongest.Name = "chkCycleFastestLongest"; + this.chkCycleFastestLongest.Size = new System.Drawing.Size(200, 24); + this.chkCycleFastestLongest.TabIndex = 0; + this.chkCycleFastestLongest.TabStop = true; + this.chkCycleFastestLongest.Text = "Cycle Fastest / Longest"; + this.chkCycleFastestLongest.UseVisualStyleBackColor = true; + // + // chkHidePercentages + // + this.chkHidePercentages.AutoSize = true; + this.chkHidePercentages.Location = new System.Drawing.Point(24, 138); + this.chkHidePercentages.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkHidePercentages.Name = "chkHidePercentages"; + this.chkHidePercentages.Size = new System.Drawing.Size(162, 24); + this.chkHidePercentages.TabIndex = 3; + this.chkHidePercentages.Text = "Hide Percentages"; + this.chkHidePercentages.UseVisualStyleBackColor = true; + // + // chkHideWinsInfo + // + this.chkHideWinsInfo.AutoSize = true; + this.chkHideWinsInfo.Location = new System.Drawing.Point(24, 32); + this.chkHideWinsInfo.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkHideWinsInfo.Name = "chkHideWinsInfo"; + this.chkHideWinsInfo.Size = new System.Drawing.Size(137, 24); + this.chkHideWinsInfo.TabIndex = 0; + this.chkHideWinsInfo.Text = "Hide Wins info"; + this.chkHideWinsInfo.UseVisualStyleBackColor = true; + // + // cboOverlayColor + // + this.cboOverlayColor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboOverlayColor.FormattingEnabled = true; + this.cboOverlayColor.Items.AddRange(new object[] { + "Transparent", + "Magenta", + "Red", + "Green", + "Blue", + "Black"}); + this.cboOverlayColor.Location = new System.Drawing.Point(670, 231); + this.cboOverlayColor.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cboOverlayColor.Name = "cboOverlayColor"; + this.cboOverlayColor.Size = new System.Drawing.Size(272, 28); + this.cboOverlayColor.TabIndex = 19; + // + // lblOverlayColor + // + this.lblOverlayColor.AutoSize = true; + this.lblOverlayColor.Location = new System.Drawing.Point(564, 235); + this.lblOverlayColor.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblOverlayColor.Name = "lblOverlayColor"; + this.lblOverlayColor.Size = new System.Drawing.Size(95, 20); + this.lblOverlayColor.TabIndex = 18; + this.lblOverlayColor.Text = "Background"; + // + // chkFlipped + // + this.chkFlipped.AutoSize = true; + this.chkFlipped.Location = new System.Drawing.Point(670, 278); + this.chkFlipped.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkFlipped.Name = "chkFlipped"; + this.chkFlipped.Size = new System.Drawing.Size(195, 24); + this.chkFlipped.TabIndex = 20; + this.chkFlipped.Text = "Flip display horizontally"; + this.chkFlipped.UseVisualStyleBackColor = true; + // + // chkShowTabs + // + this.chkShowTabs.AutoSize = true; + this.chkShowTabs.Location = new System.Drawing.Point(24, 174); + this.chkShowTabs.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkShowTabs.Name = "chkShowTabs"; + this.chkShowTabs.Size = new System.Drawing.Size(217, 24); + this.chkShowTabs.TabIndex = 4; + this.chkShowTabs.Text = "Show Tab for current filter"; + this.chkShowTabs.UseVisualStyleBackColor = true; + // + // chkHideTimeInfo + // + this.chkHideTimeInfo.AutoSize = true; + this.chkHideTimeInfo.Location = new System.Drawing.Point(24, 103); + this.chkHideTimeInfo.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkHideTimeInfo.Name = "chkHideTimeInfo"; + this.chkHideTimeInfo.Size = new System.Drawing.Size(136, 24); + this.chkHideTimeInfo.TabIndex = 2; + this.chkHideTimeInfo.Text = "Hide Time info"; + this.chkHideTimeInfo.UseVisualStyleBackColor = true; + // + // chkHideRoundInfo + // + this.chkHideRoundInfo.AutoSize = true; + this.chkHideRoundInfo.Location = new System.Drawing.Point(24, 68); + this.chkHideRoundInfo.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkHideRoundInfo.Name = "chkHideRoundInfo"; + this.chkHideRoundInfo.Size = new System.Drawing.Size(150, 24); + this.chkHideRoundInfo.TabIndex = 1; + this.chkHideRoundInfo.Text = "Hide Round info"; + this.chkHideRoundInfo.UseVisualStyleBackColor = true; + // + // cboFastestFilter + // + this.cboFastestFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboFastestFilter.FormattingEnabled = true; + this.cboFastestFilter.Items.AddRange(new object[] { + "All Time Stats", + "Stats and Party Filter", + "Season Stats", + "Week Stats", + "Day Stats", + "Session Stats"}); + this.cboFastestFilter.Location = new System.Drawing.Point(670, 106); + this.cboFastestFilter.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cboFastestFilter.Name = "cboFastestFilter"; + this.cboFastestFilter.Size = new System.Drawing.Size(272, 28); + this.cboFastestFilter.TabIndex = 17; + // + // lblFastestFilter + // + this.lblFastestFilter.AutoSize = true; + this.lblFastestFilter.Location = new System.Drawing.Point(489, 111); + this.lblFastestFilter.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblFastestFilter.Name = "lblFastestFilter"; + this.lblFastestFilter.Size = new System.Drawing.Size(172, 20); + this.lblFastestFilter.TabIndex = 16; + this.lblFastestFilter.Text = "Fastest / Longest Filter"; + // + // cboQualifyFilter + // + this.cboQualifyFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboQualifyFilter.FormattingEnabled = true; + this.cboQualifyFilter.Items.AddRange(new object[] { + "All Time Stats", + "Stats and Party Filter", + "Season Stats", + "Week Stats", + "Day Stats", + "Session Stats"}); + this.cboQualifyFilter.Location = new System.Drawing.Point(670, 68); + this.cboQualifyFilter.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cboQualifyFilter.Name = "cboQualifyFilter"; + this.cboQualifyFilter.Size = new System.Drawing.Size(272, 28); + this.cboQualifyFilter.TabIndex = 15; + // + // lblQualifyFilter + // + this.lblQualifyFilter.AutoSize = true; + this.lblQualifyFilter.Location = new System.Drawing.Point(516, 72); + this.lblQualifyFilter.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblQualifyFilter.Name = "lblQualifyFilter"; + this.lblQualifyFilter.Size = new System.Drawing.Size(142, 20); + this.lblQualifyFilter.TabIndex = 14; + this.lblQualifyFilter.Text = "Qualify / Gold Filter"; + // + // cboWinsFilter + // + this.cboWinsFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboWinsFilter.FormattingEnabled = true; + this.cboWinsFilter.Items.AddRange(new object[] { + "All Time Stats", + "Stats and Party Filter", + "Season Stats", + "Week Stats", + "Day Stats", + "Session Stats"}); + this.cboWinsFilter.Location = new System.Drawing.Point(670, 29); + this.cboWinsFilter.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cboWinsFilter.Name = "cboWinsFilter"; + this.cboWinsFilter.Size = new System.Drawing.Size(272, 28); + this.cboWinsFilter.TabIndex = 13; + // + // lblWinsFilter + // + this.lblWinsFilter.AutoSize = true; + this.lblWinsFilter.Location = new System.Drawing.Point(528, 34); + this.lblWinsFilter.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblWinsFilter.Name = "lblWinsFilter"; + this.lblWinsFilter.Size = new System.Drawing.Size(129, 20); + this.lblWinsFilter.TabIndex = 12; + this.lblWinsFilter.Text = "Wins / Final Filter"; + // + // chkOverlayOnTop + // + this.chkOverlayOnTop.AutoSize = true; + this.chkOverlayOnTop.Location = new System.Drawing.Point(670, 314); + this.chkOverlayOnTop.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOverlayOnTop.Name = "chkOverlayOnTop"; + this.chkOverlayOnTop.Size = new System.Drawing.Size(174, 24); + this.chkOverlayOnTop.TabIndex = 21; + this.chkOverlayOnTop.Text = "Always show on top"; + this.chkOverlayOnTop.UseVisualStyleBackColor = true; + // + // chkUseNDI + // + this.chkUseNDI.AutoSize = true; + this.chkUseNDI.Location = new System.Drawing.Point(670, 349); + this.chkUseNDI.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkUseNDI.Name = "chkUseNDI"; + this.chkUseNDI.Size = new System.Drawing.Size(231, 24); + this.chkUseNDI.TabIndex = 22; + this.chkUseNDI.Text = "Use NDI to transmit Overlay"; + this.chkUseNDI.UseVisualStyleBackColor = true; + // + // lblCycleTimeSecondsTag + // + this.lblCycleTimeSecondsTag.AutoSize = true; + this.lblCycleTimeSecondsTag.Location = new System.Drawing.Point(188, 232); + this.lblCycleTimeSecondsTag.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblCycleTimeSecondsTag.Name = "lblCycleTimeSecondsTag"; + this.lblCycleTimeSecondsTag.Size = new System.Drawing.Size(34, 20); + this.lblCycleTimeSecondsTag.TabIndex = 7; + this.lblCycleTimeSecondsTag.Text = "sec"; + // + // lblCycleTimeSeconds + // + this.lblCycleTimeSeconds.AutoSize = true; + this.lblCycleTimeSeconds.Location = new System.Drawing.Point(28, 232); + this.lblCycleTimeSeconds.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblCycleTimeSeconds.Name = "lblCycleTimeSeconds"; + this.lblCycleTimeSeconds.Size = new System.Drawing.Size(85, 20); + this.lblCycleTimeSeconds.TabIndex = 5; + this.lblCycleTimeSeconds.Text = "Cycle Time"; + // + // txtCycleTimeSeconds + // + this.txtCycleTimeSeconds.Location = new System.Drawing.Point(126, 228); + this.txtCycleTimeSeconds.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.txtCycleTimeSeconds.MaxLength = 2; + this.txtCycleTimeSeconds.Name = "txtCycleTimeSeconds"; + this.txtCycleTimeSeconds.Size = new System.Drawing.Size(50, 26); + this.txtCycleTimeSeconds.TabIndex = 6; + this.txtCycleTimeSeconds.Text = "5"; + this.txtCycleTimeSeconds.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.txtCycleTimeSeconds.Validating += new System.ComponentModel.CancelEventHandler(this.txtCycleTimeSeconds_Validating); + // + // grpCycleWinFinalStreak + // + this.grpCycleWinFinalStreak.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.grpCycleWinFinalStreak.Controls.Add(this.chkOnlyShowFinalStreak); + this.grpCycleWinFinalStreak.Controls.Add(this.chkOnlyShowWinStreak); + this.grpCycleWinFinalStreak.Controls.Add(this.chkCycleWinFinalStreak); + this.grpCycleWinFinalStreak.Location = new System.Drawing.Point(18, 340); + this.grpCycleWinFinalStreak.Margin = new System.Windows.Forms.Padding(0); + this.grpCycleWinFinalStreak.Name = "grpCycleWinFinalStreak"; + this.grpCycleWinFinalStreak.Size = new System.Drawing.Size(554, 49); + this.grpCycleWinFinalStreak.TabIndex = 10; + this.grpCycleWinFinalStreak.TabStop = false; + // + // chkOnlyShowFinalStreak + // + this.chkOnlyShowFinalStreak.AutoSize = true; + this.chkOnlyShowFinalStreak.Location = new System.Drawing.Point(388, 15); + this.chkOnlyShowFinalStreak.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowFinalStreak.Name = "chkOnlyShowFinalStreak"; + this.chkOnlyShowFinalStreak.Size = new System.Drawing.Size(154, 24); + this.chkOnlyShowFinalStreak.TabIndex = 2; + this.chkOnlyShowFinalStreak.Text = "Final Streak Only"; + this.chkOnlyShowFinalStreak.UseVisualStyleBackColor = true; + // + // chkOnlyShowWinStreak + // + this.chkOnlyShowWinStreak.AutoSize = true; + this.chkOnlyShowWinStreak.Location = new System.Drawing.Point(226, 15); + this.chkOnlyShowWinStreak.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowWinStreak.Name = "chkOnlyShowWinStreak"; + this.chkOnlyShowWinStreak.Size = new System.Drawing.Size(147, 24); + this.chkOnlyShowWinStreak.TabIndex = 1; + this.chkOnlyShowWinStreak.Text = "Win Streak Only"; + this.chkOnlyShowWinStreak.UseVisualStyleBackColor = true; + // + // chkCycleWinFinalStreak + // + this.chkCycleWinFinalStreak.AutoSize = true; + this.chkCycleWinFinalStreak.Checked = true; + this.chkCycleWinFinalStreak.Location = new System.Drawing.Point(8, 15); + this.chkCycleWinFinalStreak.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkCycleWinFinalStreak.Name = "chkCycleWinFinalStreak"; + this.chkCycleWinFinalStreak.Size = new System.Drawing.Size(200, 24); + this.chkCycleWinFinalStreak.TabIndex = 0; + this.chkCycleWinFinalStreak.TabStop = true; + this.chkCycleWinFinalStreak.Text = "Cycle Win / Final Streak"; + this.chkCycleWinFinalStreak.UseVisualStyleBackColor = true; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.chkOnlyShowPing); + this.groupBox1.Controls.Add(this.chkOnlyShowPlayers); + this.groupBox1.Controls.Add(this.chkCyclePlayersPing); + this.groupBox1.Location = new System.Drawing.Point(18, 378); + this.groupBox1.Margin = new System.Windows.Forms.Padding(0); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(554, 49); + this.groupBox1.TabIndex = 11; + this.groupBox1.TabStop = false; + // + // chkOnlyShowPing + // + this.chkOnlyShowPing.AutoSize = true; + this.chkOnlyShowPing.Location = new System.Drawing.Point(388, 15); + this.chkOnlyShowPing.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowPing.Name = "chkOnlyShowPing"; + this.chkOnlyShowPing.Size = new System.Drawing.Size(100, 24); + this.chkOnlyShowPing.TabIndex = 2; + this.chkOnlyShowPing.Text = "Ping Only"; + this.chkOnlyShowPing.UseVisualStyleBackColor = true; + // + // chkOnlyShowPlayers + // + this.chkOnlyShowPlayers.AutoSize = true; + this.chkOnlyShowPlayers.Location = new System.Drawing.Point(226, 15); + this.chkOnlyShowPlayers.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkOnlyShowPlayers.Name = "chkOnlyShowPlayers"; + this.chkOnlyShowPlayers.Size = new System.Drawing.Size(120, 24); + this.chkOnlyShowPlayers.TabIndex = 1; + this.chkOnlyShowPlayers.Text = "Players Only"; + this.chkOnlyShowPlayers.UseVisualStyleBackColor = true; + // + // chkCyclePlayersPing + // + this.chkCyclePlayersPing.AutoSize = true; + this.chkCyclePlayersPing.Checked = true; + this.chkCyclePlayersPing.Location = new System.Drawing.Point(8, 15); + this.chkCyclePlayersPing.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkCyclePlayersPing.Name = "chkCyclePlayersPing"; + this.chkCyclePlayersPing.Size = new System.Drawing.Size(170, 24); + this.chkCyclePlayersPing.TabIndex = 0; + this.chkCyclePlayersPing.TabStop = true; + this.chkCyclePlayersPing.Text = "Cycle Players / Ping"; + this.chkCyclePlayersPing.UseVisualStyleBackColor = true; + // + // lblOverlayFont + // + this.lblOverlayFont.AutoSize = true; + this.lblOverlayFont.Location = new System.Drawing.Point(14, 437); + this.lblOverlayFont.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblOverlayFont.Name = "lblOverlayFont"; + this.lblOverlayFont.Size = new System.Drawing.Size(157, 20); + this.lblOverlayFont.TabIndex = 23; + this.lblOverlayFont.Text = "Custom Overlay Font"; + // + // btnSelectFont + // + this.btnSelectFont.Location = new System.Drawing.Point(40, 460); + this.btnSelectFont.Name = "btnSelectFont"; + this.btnSelectFont.Size = new System.Drawing.Size(105, 31); + this.btnSelectFont.TabIndex = 24; + this.btnSelectFont.Text = "Select Font"; + this.btnSelectFont.UseVisualStyleBackColor = true; + this.btnSelectFont.Click += new System.EventHandler(this.btnSelectFont_Click); + // + // btnResetOverlayFont + // + this.btnResetOverlayFont.Location = new System.Drawing.Point(40, 497); + this.btnResetOverlayFont.Name = "btnResetOverlayFont"; + this.btnResetOverlayFont.Size = new System.Drawing.Size(105, 31); + this.btnResetOverlayFont.TabIndex = 25; + this.btnResetOverlayFont.Text = "Reset Font"; + this.btnResetOverlayFont.UseVisualStyleBackColor = true; + this.btnResetOverlayFont.Click += new System.EventHandler(this.btnResetOverlayFont_Click); + // + // grpOverlayFontExample + // + this.grpOverlayFontExample.Controls.Add(this.lblOverlayFontExample); + this.grpOverlayFontExample.Location = new System.Drawing.Point(178, 437); + this.grpOverlayFontExample.Name = "grpOverlayFontExample"; + this.grpOverlayFontExample.Size = new System.Drawing.Size(776, 91); + this.grpOverlayFontExample.TabIndex = 35; + this.grpOverlayFontExample.TabStop = false; + this.grpOverlayFontExample.Text = "Example"; + // + // lblOverlayFontExample + // + this.lblOverlayFontExample.Location = new System.Drawing.Point(6, 23); + this.lblOverlayFontExample.Name = "lblOverlayFontExample"; + this.lblOverlayFontExample.Size = new System.Drawing.Size(764, 52); + this.lblOverlayFontExample.TabIndex = 0; + this.lblOverlayFontExample.Text = "Round 3: Freezy Peak"; + this.lblOverlayFontExample.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // grpStats + // + this.grpStats.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.grpStats.Controls.Add(this.chkChangeHoopsieLegends); + this.grpStats.Controls.Add(this.chkAutoUpdate); + this.grpStats.Controls.Add(this.lblPreviousWinsNote); + this.grpStats.Controls.Add(this.lblPreviousWins); + this.grpStats.Controls.Add(this.txtPreviousWins); + this.grpStats.Location = new System.Drawing.Point(18, 89); + this.grpStats.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.grpStats.Name = "grpStats"; + this.grpStats.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.grpStats.Size = new System.Drawing.Size(968, 78); + this.grpStats.TabIndex = 3; + this.grpStats.TabStop = false; + this.grpStats.Text = "Stats"; + // + // chkChangeHoopsieLegends + // + this.chkChangeHoopsieLegends.AutoSize = true; + this.chkChangeHoopsieLegends.Location = new System.Drawing.Point(585, 32); + this.chkChangeHoopsieLegends.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkChangeHoopsieLegends.Name = "chkChangeHoopsieLegends"; + this.chkChangeHoopsieLegends.Size = new System.Drawing.Size(362, 24); + this.chkChangeHoopsieLegends.TabIndex = 4; + this.chkChangeHoopsieLegends.Text = "Rename Hoopsie Legends to Hoopsie Heroes"; + this.chkChangeHoopsieLegends.UseVisualStyleBackColor = true; + // + // chkAutoUpdate + // + this.chkAutoUpdate.AutoSize = true; + this.chkAutoUpdate.Location = new System.Drawing.Point(381, 32); + this.chkAutoUpdate.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chkAutoUpdate.Name = "chkAutoUpdate"; + this.chkAutoUpdate.Size = new System.Drawing.Size(190, 24); + this.chkAutoUpdate.TabIndex = 3; + this.chkAutoUpdate.Text = "Auto Update Program"; + this.chkAutoUpdate.UseVisualStyleBackColor = true; + // + // lblPreviousWinsNote + // + this.lblPreviousWinsNote.AutoSize = true; + this.lblPreviousWinsNote.ForeColor = System.Drawing.Color.DimGray; + this.lblPreviousWinsNote.Location = new System.Drawing.Point(200, 34); + this.lblPreviousWinsNote.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblPreviousWinsNote.Name = "lblPreviousWinsNote"; + this.lblPreviousWinsNote.Size = new System.Drawing.Size(162, 20); + this.lblPreviousWinsNote.TabIndex = 2; + this.lblPreviousWinsNote.Text = "(Before using tracker)"; + // + // lblPreviousWins + // + this.lblPreviousWins.AutoSize = true; + this.lblPreviousWins.Location = new System.Drawing.Point(16, 34); + this.lblPreviousWins.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblPreviousWins.Name = "lblPreviousWins"; + this.lblPreviousWins.Size = new System.Drawing.Size(108, 20); + this.lblPreviousWins.TabIndex = 0; + this.lblPreviousWins.Text = "Previous Wins"; + // + // txtPreviousWins + // + this.txtPreviousWins.Location = new System.Drawing.Point(138, 29); + this.txtPreviousWins.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.txtPreviousWins.MaxLength = 4; + this.txtPreviousWins.Name = "txtPreviousWins"; + this.txtPreviousWins.Size = new System.Drawing.Size(50, 26); + this.txtPreviousWins.TabIndex = 1; + this.txtPreviousWins.Text = "0"; + this.txtPreviousWins.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.txtPreviousWins.Validating += new System.ComponentModel.CancelEventHandler(this.txtPreviousWins_Validating); + // + // grpGameOptions + // + this.grpGameOptions.Controls.Add(this.lblGameExeLocation); + this.grpGameOptions.Controls.Add(this.txtGameExeLocation); + this.grpGameOptions.Controls.Add(this.btnGameExeLocationBrowse); + this.grpGameOptions.Controls.Add(this.chkAutoLaunchGameOnStart); + this.grpGameOptions.Location = new System.Drawing.Point(18, 802); + this.grpGameOptions.Name = "grpGameOptions"; + this.grpGameOptions.Size = new System.Drawing.Size(966, 106); + this.grpGameOptions.TabIndex = 6; + this.grpGameOptions.TabStop = false; + this.grpGameOptions.Text = "Game Options"; + // + // lblGameExeLocation + // + this.lblGameExeLocation.AutoSize = true; + this.lblGameExeLocation.Location = new System.Drawing.Point(16, 32); + this.lblGameExeLocation.Name = "lblGameExeLocation"; + this.lblGameExeLocation.Size = new System.Drawing.Size(188, 20); + this.lblGameExeLocation.TabIndex = 0; + this.lblGameExeLocation.Text = "Fall Guys Game Location"; + // + // txtGameExeLocation + // + this.txtGameExeLocation.Enabled = false; + this.txtGameExeLocation.Location = new System.Drawing.Point(210, 29); + this.txtGameExeLocation.Name = "txtGameExeLocation"; + this.txtGameExeLocation.Size = new System.Drawing.Size(668, 26); + this.txtGameExeLocation.TabIndex = 1; + // + // btnGameExeLocationBrowse + // + this.btnGameExeLocationBrowse.Location = new System.Drawing.Point(884, 28); + this.btnGameExeLocationBrowse.Name = "btnGameExeLocationBrowse"; + this.btnGameExeLocationBrowse.Size = new System.Drawing.Size(75, 34); + this.btnGameExeLocationBrowse.TabIndex = 2; + this.btnGameExeLocationBrowse.Text = "Browse"; + this.btnGameExeLocationBrowse.UseVisualStyleBackColor = true; + this.btnGameExeLocationBrowse.Click += new System.EventHandler(this.btnGameExeLocationBrowse_Click); + // + // chkAutoLaunchGameOnStart + // + this.chkAutoLaunchGameOnStart.Location = new System.Drawing.Point(20, 66); + this.chkAutoLaunchGameOnStart.Name = "chkAutoLaunchGameOnStart"; + this.chkAutoLaunchGameOnStart.Size = new System.Drawing.Size(306, 25); + this.chkAutoLaunchGameOnStart.TabIndex = 3; + this.chkAutoLaunchGameOnStart.Text = "Auto-launch Fall Guys on tracker start"; + // + // grpSortingOptions + // + this.grpSortingOptions.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.grpSortingOptions.Controls.Add(this.chkIgnoreLevelTypeWhenSorting); + this.grpSortingOptions.Location = new System.Drawing.Point(18, 728); + this.grpSortingOptions.Name = "grpSortingOptions"; + this.grpSortingOptions.Size = new System.Drawing.Size(968, 68); + this.grpSortingOptions.TabIndex = 5; + this.grpSortingOptions.TabStop = false; + this.grpSortingOptions.Text = "Sorting Options"; + // + // chkIgnoreLevelTypeWhenSorting + // + this.chkIgnoreLevelTypeWhenSorting.AutoSize = true; + this.chkIgnoreLevelTypeWhenSorting.Location = new System.Drawing.Point(24, 29); + this.chkIgnoreLevelTypeWhenSorting.Name = "chkIgnoreLevelTypeWhenSorting"; + this.chkIgnoreLevelTypeWhenSorting.Size = new System.Drawing.Size(254, 24); + this.chkIgnoreLevelTypeWhenSorting.TabIndex = 0; + this.chkIgnoreLevelTypeWhenSorting.Text = "Ignore Level Type when sorting"; + this.chkIgnoreLevelTypeWhenSorting.UseVisualStyleBackColor = true; + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(872, 917); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(112, 35); + this.btnCancel.TabIndex = 8; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // trkOverlayScale + // + this.trkOverlayScale.Location = new System.Drawing.Point(670, 146); + this.trkOverlayScale.Maximum = 25; + this.trkOverlayScale.Minimum = 5; + this.trkOverlayScale.Name = "trkOverlayScale"; + this.trkOverlayScale.Size = new System.Drawing.Size(217, 69); + this.trkOverlayScale.TabIndex = 36; + this.trkOverlayScale.TickStyle = System.Windows.Forms.TickStyle.Both; + this.trkOverlayScale.Value = 10; + this.trkOverlayScale.ValueChanged += new System.EventHandler(this.trkOverlayScale_ValueChanged); + // + // lblOverlayScaling + // + this.lblOverlayScaling.AutoSize = true; + this.lblOverlayScaling.Location = new System.Drawing.Point(466, 160); + this.lblOverlayScaling.Name = "lblOverlayScaling"; + this.lblOverlayScaling.Size = new System.Drawing.Size(117, 20); + this.lblOverlayScaling.TabIndex = 37; + this.lblOverlayScaling.Text = "Overlay Scaling"; + // + // lblOverlayScale + // + this.lblOverlayScale.Location = new System.Drawing.Point(884, 159); + this.lblOverlayScale.Name = "lblOverlayScale"; + this.lblOverlayScale.Size = new System.Drawing.Size(58, 23); + this.lblOverlayScale.TabIndex = 38; + this.lblOverlayScale.Text = "%"; + this.lblOverlayScale.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // btnOverlayScaleReset + // + this.btnOverlayScaleReset.Location = new System.Drawing.Point(589, 155); + this.btnOverlayScaleReset.Name = "btnOverlayScaleReset"; + this.btnOverlayScaleReset.Size = new System.Drawing.Size(75, 31); + this.btnOverlayScaleReset.TabIndex = 39; + this.btnOverlayScaleReset.Text = "Reset"; + this.btnOverlayScaleReset.UseVisualStyleBackColor = true; + this.btnOverlayScaleReset.Click += new System.EventHandler(this.btnOverlayScaleReset_Click); + // + // Settings + // + this.AcceptButton = this.btnSave; + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(242)))), ((int)(((byte)(251))))); + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(1004, 965); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.grpGameOptions); + this.Controls.Add(this.grpSortingOptions); + this.Controls.Add(this.grpOverlay); + this.Controls.Add(this.grpStats); + this.Controls.Add(this.btnSave); + this.Controls.Add(this.txtLogPath); + this.Controls.Add(this.lblLogPathNote); + this.Controls.Add(this.lblLogPath); + this.ForeColor = System.Drawing.Color.Black; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "Settings"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Settings"; + this.Load += new System.EventHandler(this.Settings_Load); + this.grpOverlay.ResumeLayout(false); + this.grpOverlay.PerformLayout(); + this.grpCycleQualifyGold.ResumeLayout(false); + this.grpCycleQualifyGold.PerformLayout(); + this.grpCycleFastestLongest.ResumeLayout(false); + this.grpCycleFastestLongest.PerformLayout(); + this.grpCycleWinFinalStreak.ResumeLayout(false); + this.grpCycleWinFinalStreak.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.grpOverlayFontExample.ResumeLayout(false); + this.grpStats.ResumeLayout(false); + this.grpStats.PerformLayout(); + this.grpGameOptions.ResumeLayout(false); + this.grpGameOptions.PerformLayout(); + this.grpSortingOptions.ResumeLayout(false); + this.grpSortingOptions.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trkOverlayScale)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lblLogPath; + private System.Windows.Forms.Label lblLogPathNote; + private System.Windows.Forms.TextBox txtLogPath; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.GroupBox grpOverlay; + private System.Windows.Forms.Label lblCycleTimeSecondsTag; + private System.Windows.Forms.Label lblCycleTimeSeconds; + private System.Windows.Forms.TextBox txtCycleTimeSeconds; + private System.Windows.Forms.GroupBox grpStats; + private System.Windows.Forms.Label lblPreviousWinsNote; + private System.Windows.Forms.Label lblPreviousWins; + private System.Windows.Forms.TextBox txtPreviousWins; + private System.Windows.Forms.CheckBox chkUseNDI; + private System.Windows.Forms.CheckBox chkOverlayOnTop; + private System.Windows.Forms.ComboBox cboFastestFilter; + private System.Windows.Forms.Label lblFastestFilter; + private System.Windows.Forms.ComboBox cboQualifyFilter; + private System.Windows.Forms.Label lblQualifyFilter; + private System.Windows.Forms.ComboBox cboWinsFilter; + private System.Windows.Forms.Label lblWinsFilter; + private System.Windows.Forms.CheckBox chkHideTimeInfo; + private System.Windows.Forms.CheckBox chkHideRoundInfo; + private System.Windows.Forms.CheckBox chkShowTabs; + private System.Windows.Forms.CheckBox chkAutoUpdate; + private System.Windows.Forms.ComboBox cboOverlayColor; + private System.Windows.Forms.Label lblOverlayColor; + private System.Windows.Forms.CheckBox chkFlipped; + private System.Windows.Forms.CheckBox chkHideWinsInfo; + private System.Windows.Forms.CheckBox chkHidePercentages; + private System.Windows.Forms.CheckBox chkChangeHoopsieLegends; + private System.Windows.Forms.GroupBox grpGameOptions; + private System.Windows.Forms.Label lblGameExeLocation; + private System.Windows.Forms.TextBox txtGameExeLocation; + private System.Windows.Forms.Button btnGameExeLocationBrowse; + private System.Windows.Forms.CheckBox chkAutoLaunchGameOnStart; + private System.Windows.Forms.GroupBox grpSortingOptions; + private System.Windows.Forms.CheckBox chkIgnoreLevelTypeWhenSorting; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.GroupBox grpCycleQualifyGold; + private System.Windows.Forms.RadioButton chkOnlyShowGold; + private System.Windows.Forms.RadioButton chkOnlyShowQualify; + private System.Windows.Forms.RadioButton chkCycleQualifyGold; + private System.Windows.Forms.GroupBox grpCycleFastestLongest; + private System.Windows.Forms.RadioButton chkOnlyShowLongest; + private System.Windows.Forms.RadioButton chkOnlyShowFastest; + private System.Windows.Forms.RadioButton chkCycleFastestLongest; + private System.Windows.Forms.GroupBox grpCycleWinFinalStreak; + private System.Windows.Forms.RadioButton chkOnlyShowFinalStreak; + private System.Windows.Forms.RadioButton chkOnlyShowWinStreak; + private System.Windows.Forms.RadioButton chkCycleWinFinalStreak; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.RadioButton chkOnlyShowPing; + private System.Windows.Forms.RadioButton chkOnlyShowPlayers; + private System.Windows.Forms.RadioButton chkCyclePlayersPing; + private System.Windows.Forms.Label lblOverlayFont; + private System.Windows.Forms.Button btnSelectFont; + private System.Windows.Forms.FontDialog dlgOverlayFont; + private System.Windows.Forms.Label lblOverlayFontExample; + private System.Windows.Forms.GroupBox grpOverlayFontExample; + private System.Windows.Forms.Button btnResetOverlayFont; + private System.Windows.Forms.Label lblOverlayScale; + private System.Windows.Forms.Label lblOverlayScaling; + private System.Windows.Forms.TrackBar trkOverlayScale; + private System.Windows.Forms.Button btnOverlayScaleReset; + } +} \ No newline at end of file diff --git a/Views/Settings.cs b/Views/Settings.cs new file mode 100644 index 000000000..0d6702acb --- /dev/null +++ b/Views/Settings.cs @@ -0,0 +1,335 @@ +using System; +using System.Drawing; +using System.Drawing.Text; +using System.IO; +using System.Windows.Forms; +namespace FallGuysStats { + public partial class Settings : Form { + private PrivateFontCollection CustomFonts; + private string overlayFontSerialized = string.Empty; + + public UserSettings CurrentSettings { get; set; } + public Settings() { + InitializeComponent(); + + CustomFonts = new PrivateFontCollection(); + CustomFonts.AddFontFile("TitanOne-Regular.ttf"); + } + private void Settings_Load(object sender, EventArgs e) { + txtLogPath.Text = CurrentSettings.LogPath; + + if (CurrentSettings.SwitchBetweenLongest) { + chkCycleFastestLongest.Checked = true; + } else if (CurrentSettings.OnlyShowLongest) { + chkOnlyShowLongest.Checked = true; + } else { + chkOnlyShowFastest.Checked = true; + } + if (CurrentSettings.SwitchBetweenQualify) { + chkCycleQualifyGold.Checked = true; + } else if (CurrentSettings.OnlyShowGold) { + chkOnlyShowGold.Checked = true; + } else { + chkOnlyShowQualify.Checked = true; + } + if (CurrentSettings.SwitchBetweenPlayers) { + chkCyclePlayersPing.Checked = true; + } else if (CurrentSettings.OnlyShowPing) { + chkOnlyShowPing.Checked = true; + } else { + chkOnlyShowPlayers.Checked = true; + } + if (CurrentSettings.SwitchBetweenStreaks) { + chkCycleWinFinalStreak.Checked = true; + } else if (CurrentSettings.OnlyShowFinalStreak) { + chkOnlyShowFinalStreak.Checked = true; + } else { + chkOnlyShowWinStreak.Checked = true; + } + + txtCycleTimeSeconds.Text = CurrentSettings.CycleTimeSeconds.ToString(); + txtPreviousWins.Text = CurrentSettings.PreviousWins.ToString(); + chkUseNDI.Checked = CurrentSettings.UseNDI; + chkOverlayOnTop.Checked = !CurrentSettings.OverlayNotOnTop; + chkHideWinsInfo.Checked = CurrentSettings.HideWinsInfo; + chkHideRoundInfo.Checked = CurrentSettings.HideRoundInfo; + chkHideTimeInfo.Checked = CurrentSettings.HideTimeInfo; + chkShowTabs.Checked = CurrentSettings.ShowOverlayTabs; + chkAutoUpdate.Checked = CurrentSettings.AutoUpdate; + chkFlipped.Checked = CurrentSettings.FlippedDisplay; + chkHidePercentages.Checked = CurrentSettings.HideOverlayPercentages; + chkChangeHoopsieLegends.Checked = CurrentSettings.HoopsieHeros; + + switch (CurrentSettings.OverlayColor) { + case 0: cboOverlayColor.SelectedItem = "Magenta"; break; + case 1: cboOverlayColor.SelectedItem = "Blue"; break; + case 2: cboOverlayColor.SelectedItem = "Red"; break; + case 3: cboOverlayColor.SelectedItem = "Transparent"; break; + case 4: cboOverlayColor.SelectedItem = "Black"; break; + case 5: cboOverlayColor.SelectedItem = "Green"; break; + } + switch (CurrentSettings.WinsFilter) { + case 0: cboWinsFilter.SelectedItem = "Stats and Party Filter"; break; + case 1: cboWinsFilter.SelectedItem = "Season Stats"; break; + case 2: cboWinsFilter.SelectedItem = "Week Stats"; break; + case 3: cboWinsFilter.SelectedItem = "All Time Stats"; break; + case 4: cboWinsFilter.SelectedItem = "Day Stats"; break; + case 5: cboWinsFilter.SelectedItem = "Session Stats"; break; + } + switch (CurrentSettings.QualifyFilter) { + case 0: cboQualifyFilter.SelectedItem = "All Time Stats"; break; + case 1: cboQualifyFilter.SelectedItem = "Stats and Party Filter"; break; + case 2: cboQualifyFilter.SelectedItem = "Season Stats"; break; + case 3: cboQualifyFilter.SelectedItem = "Week Stats"; break; + case 4: cboQualifyFilter.SelectedItem = "Day Stats"; break; + case 5: cboQualifyFilter.SelectedItem = "Session Stats"; break; + } + switch (CurrentSettings.FastestFilter) { + case 0: cboFastestFilter.SelectedItem = "All Time Stats"; break; + case 1: cboFastestFilter.SelectedItem = "Stats and Party Filter"; break; + case 2: cboFastestFilter.SelectedItem = "Season Stats"; break; + case 3: cboFastestFilter.SelectedItem = "Week Stats"; break; + case 4: cboFastestFilter.SelectedItem = "Day Stats"; break; + case 5: cboFastestFilter.SelectedItem = "Session Stats"; break; + } + + txtGameExeLocation.Text = CurrentSettings.GameExeLocation; + chkAutoLaunchGameOnStart.Checked = CurrentSettings.AutoLaunchGameOnStartup; + chkIgnoreLevelTypeWhenSorting.Checked = CurrentSettings.IgnoreLevelTypeWhenSorting; + + if((CurrentSettings.OverlayScale < (trkOverlayScale.Minimum / 10.0)) || (CurrentSettings.OverlayScale > (trkOverlayScale.Maximum / 10.0))) { + CurrentSettings.OverlayScale = 1.0; + } + + this.trkOverlayScale.Value = (int)(CurrentSettings.OverlayScale * 10); + this.lblOverlayScale.Text = (CurrentSettings.OverlayScale * 100) + "%"; + + if (!string.IsNullOrEmpty(CurrentSettings.OverlayFontSerialized)) { + FontConverter fontConverter = new FontConverter(); + Font exampleFont = fontConverter.ConvertFromString(CurrentSettings.OverlayFontSerialized) as Font; + lblOverlayFontExample.Font = exampleFont; + } else if (CustomFonts != null) { + lblOverlayFontExample.Font = new Font(CustomFonts.Families[0], 18, FontStyle.Regular, GraphicsUnit.Pixel); + } + } + + private void btnSave_Click(object sender, EventArgs e) { + CurrentSettings.LogPath = txtLogPath.Text; + + if (string.IsNullOrEmpty(txtCycleTimeSeconds.Text)) { + CurrentSettings.CycleTimeSeconds = 5; + } else { + CurrentSettings.CycleTimeSeconds = int.Parse(txtCycleTimeSeconds.Text); + if (CurrentSettings.CycleTimeSeconds <= 0) { + CurrentSettings.CycleTimeSeconds = 5; + } + } + + if (string.IsNullOrEmpty(txtPreviousWins.Text)) { + CurrentSettings.PreviousWins = 0; + } else { + CurrentSettings.PreviousWins = int.Parse(txtPreviousWins.Text); + if (CurrentSettings.PreviousWins < 0) { + CurrentSettings.PreviousWins = 0; + } + } + + if (chkCycleFastestLongest.Checked) { + CurrentSettings.SwitchBetweenLongest = true; + CurrentSettings.OnlyShowLongest = false; + } else if (chkOnlyShowLongest.Checked) { + CurrentSettings.SwitchBetweenLongest = false; + CurrentSettings.OnlyShowLongest = true; + } else { + CurrentSettings.SwitchBetweenLongest = false; + CurrentSettings.OnlyShowLongest = false; + } + if (chkCycleQualifyGold.Checked) { + CurrentSettings.SwitchBetweenQualify = true; + CurrentSettings.OnlyShowGold = false; + } else if (chkOnlyShowGold.Checked) { + CurrentSettings.SwitchBetweenQualify = false; + CurrentSettings.OnlyShowGold = true; + } else { + CurrentSettings.SwitchBetweenQualify = false; + CurrentSettings.OnlyShowGold = false; + } + if (chkCyclePlayersPing.Checked) { + CurrentSettings.SwitchBetweenPlayers = true; + CurrentSettings.OnlyShowPing = false; + } else if (chkOnlyShowPing.Checked) { + CurrentSettings.SwitchBetweenPlayers = false; + CurrentSettings.OnlyShowPing = true; + } else { + CurrentSettings.SwitchBetweenPlayers = false; + CurrentSettings.OnlyShowPing = false; + } + if (chkCycleWinFinalStreak.Checked) { + CurrentSettings.SwitchBetweenStreaks = true; + CurrentSettings.OnlyShowFinalStreak = false; + } else if (chkOnlyShowFinalStreak.Checked) { + CurrentSettings.SwitchBetweenStreaks = false; + CurrentSettings.OnlyShowFinalStreak = true; + } else { + CurrentSettings.SwitchBetweenStreaks = false; + CurrentSettings.OnlyShowFinalStreak = false; + } + + CurrentSettings.UseNDI = chkUseNDI.Checked; + CurrentSettings.OverlayNotOnTop = !chkOverlayOnTop.Checked; + if (chkHideRoundInfo.Checked && chkHideTimeInfo.Checked && chkHideWinsInfo.Checked) { + chkHideWinsInfo.Checked = false; + } + bool resizeOverlay = CurrentSettings.HideWinsInfo != chkHideWinsInfo.Checked || + CurrentSettings.HideRoundInfo != chkHideRoundInfo.Checked || + CurrentSettings.HideTimeInfo != chkHideTimeInfo.Checked || + CurrentSettings.ShowOverlayTabs != chkShowTabs.Checked; + + CurrentSettings.HideWinsInfo = chkHideWinsInfo.Checked; + CurrentSettings.HideRoundInfo = chkHideRoundInfo.Checked; + CurrentSettings.HideTimeInfo = chkHideTimeInfo.Checked; + CurrentSettings.ShowOverlayTabs = chkShowTabs.Checked; + CurrentSettings.AutoUpdate = chkAutoUpdate.Checked; + CurrentSettings.FlippedDisplay = chkFlipped.Checked; + CurrentSettings.HideOverlayPercentages = chkHidePercentages.Checked; + CurrentSettings.HoopsieHeros = chkChangeHoopsieLegends.Checked; + + switch ((string)cboOverlayColor.SelectedItem) { + case "Magenta": CurrentSettings.OverlayColor = 0; break; + case "Blue": CurrentSettings.OverlayColor = 1; break; + case "Red": CurrentSettings.OverlayColor = 2; break; + case "Transparent": CurrentSettings.OverlayColor = 3; break; + case "Black": CurrentSettings.OverlayColor = 4; break; + case "Green": CurrentSettings.OverlayColor = 5; break; + } + switch ((string)cboWinsFilter.SelectedItem) { + case "Stats and Party Filter": CurrentSettings.WinsFilter = 0; break; + case "Season Stats": CurrentSettings.WinsFilter = 1; break; + case "Week Stats": CurrentSettings.WinsFilter = 2; break; + case "All Time Stats": CurrentSettings.WinsFilter = 3; break; + case "Day Stats": CurrentSettings.WinsFilter = 4; break; + case "Session Stats": CurrentSettings.WinsFilter = 5; break; + } + switch ((string)cboQualifyFilter.SelectedItem) { + case "All Time Stats": CurrentSettings.QualifyFilter = 0; break; + case "Stats and Party Filter": CurrentSettings.QualifyFilter = 1; break; + case "Season Stats": CurrentSettings.QualifyFilter = 2; break; + case "Week Stats": CurrentSettings.QualifyFilter = 3; break; + case "Day Stats": CurrentSettings.QualifyFilter = 4; break; + case "Session Stats": CurrentSettings.QualifyFilter = 5; break; + } + switch ((string)cboFastestFilter.SelectedItem) { + case "All Time Stats": CurrentSettings.FastestFilter = 0; break; + case "Stats and Party Filter": CurrentSettings.FastestFilter = 1; break; + case "Season Stats": CurrentSettings.FastestFilter = 2; break; + case "Week Stats": CurrentSettings.FastestFilter = 3; break; + case "Day Stats": CurrentSettings.FastestFilter = 4; break; + case "Session Stats": CurrentSettings.FastestFilter = 5; break; + } + + if (resizeOverlay) { + int overlaySetting = (CurrentSettings.HideWinsInfo ? 4 : 0) + (CurrentSettings.HideRoundInfo ? 2 : 0) + (CurrentSettings.HideTimeInfo ? 1 : 0); + switch (overlaySetting) { + case 0: CurrentSettings.OverlayWidth = 786; break; + case 1: CurrentSettings.OverlayWidth = 786 - 225 - 6; break; + case 2: CurrentSettings.OverlayWidth = 786 - 281 - 6; break; + case 3: CurrentSettings.OverlayWidth = 786 - 281 - 225 - 12; break; + case 4: CurrentSettings.OverlayWidth = 786 - 242 - 6; break; + case 5: CurrentSettings.OverlayWidth = 786 - 242 - 225 - 12; break; + case 6: CurrentSettings.OverlayWidth = 786 - 242 - 281 - 12; break; + } + + if (CurrentSettings.ShowOverlayTabs) { + CurrentSettings.OverlayHeight = 134; + } else { + CurrentSettings.OverlayHeight = 99; + } + } + + CurrentSettings.IgnoreLevelTypeWhenSorting = chkIgnoreLevelTypeWhenSorting.Checked; + CurrentSettings.GameExeLocation = txtGameExeLocation.Text; + CurrentSettings.AutoLaunchGameOnStartup = chkAutoLaunchGameOnStart.Checked; + + CurrentSettings.OverlayScale = trkOverlayScale.Value / 10.0; + + if (!string.IsNullOrEmpty(CurrentSettings.OverlayFontSerialized)) { + FontConverter fontConverter = new FontConverter(); + CurrentSettings.OverlayFontSerialized = fontConverter.ConvertToString(lblOverlayFontExample.Font); + } else { + CurrentSettings.OverlayFontSerialized = string.Empty; + } + + DialogResult = DialogResult.OK; + Close(); + } + private void txtCycleTimeSeconds_Validating(object sender, System.ComponentModel.CancelEventArgs e) { + if (!string.IsNullOrEmpty(txtCycleTimeSeconds.Text) && !int.TryParse(txtCycleTimeSeconds.Text, out _)) { + txtCycleTimeSeconds.Text = "5"; + } + } + private void txtLogPath_Validating(object sender, System.ComponentModel.CancelEventArgs e) { + try { + if (txtLogPath.Text.IndexOf(".log", StringComparison.OrdinalIgnoreCase) > 0) { + txtLogPath.Text = Path.GetDirectoryName(txtLogPath.Text); + } + } catch { } + } + private void txtPreviousWins_Validating(object sender, System.ComponentModel.CancelEventArgs e) { + if (!string.IsNullOrEmpty(txtPreviousWins.Text) && !int.TryParse(txtPreviousWins.Text, out _)) { + txtPreviousWins.Text = "0"; + } + } + private void btnGameExeLocationBrowse_Click(object sender, EventArgs e) { + try { + using (OpenFileDialog openFile = new OpenFileDialog()) { + FileInfo currentExeLocation = new FileInfo(txtGameExeLocation.Text); + if (currentExeLocation.Directory.Exists) { + openFile.InitialDirectory = currentExeLocation.Directory.FullName; + } + + openFile.Filter = "Exe files (*.exe)|*.exe"; + openFile.FileName = "FallGuys_client.exe"; + openFile.Title = "Locate Fall Guys"; + + DialogResult result = openFile.ShowDialog(this); + if (result.Equals(DialogResult.OK)) { + if (openFile.FileName.IndexOf("FallGuys_client.exe", StringComparison.OrdinalIgnoreCase) >= 0) { + txtGameExeLocation.Text = openFile.FileName; + } else { + MessageBox.Show("Please select \"FallGuys_client.exe\" in the install folder.", "Wrong File Selected", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } catch (Exception ex) { + ControlErrors.HandleException(this, ex, false); + } + } + private void btnCancel_Click(object sender, EventArgs e) { + DialogResult = DialogResult.Cancel; + Close(); + } + + private void btnOverlayScaleReset_Click(object sender, EventArgs e) { + trkOverlayScale.Value = 10; + } + + private void trkOverlayScale_ValueChanged(object sender, EventArgs e) { + this.lblOverlayScale.Text = (trkOverlayScale.Value * 10) + "%"; + } + private void btnSelectFont_Click(object sender, EventArgs e) { + dlgOverlayFont.Font = lblOverlayFont.Font; + DialogResult result = dlgOverlayFont.ShowDialog(this); + + if (result.Equals(DialogResult.OK)) { + lblOverlayFontExample.Font = dlgOverlayFont.Font; + FontConverter fontConverter = new FontConverter(); + overlayFontSerialized = fontConverter.ConvertToString(dlgOverlayFont.Font); + } + } + private void btnResetOverlayFont_Click(object sender, EventArgs e) { + Font defaultFont = new Font(CustomFonts.Families[0], 18, FontStyle.Regular, GraphicsUnit.Pixel); + lblOverlayFontExample.Font = defaultFont; + overlayFontSerialized = string.Empty; + } + } +} \ No newline at end of file diff --git a/Views/Settings.resx b/Views/Settings.resx new file mode 100644 index 000000000..abf0e1530 --- /dev/null +++ b/Views/Settings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/Views/Stats.Designer.cs b/Views/Stats.Designer.cs new file mode 100644 index 000000000..9d5bcc634 --- /dev/null +++ b/Views/Stats.Designer.cs @@ -0,0 +1,441 @@ +namespace FallGuysStats { + partial class Stats { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Stats)); + this.menu = new System.Windows.Forms.MenuStrip(); + this.menuSettings = new System.Windows.Forms.ToolStripMenuItem(); + this.menuFilters = new System.Windows.Forms.ToolStripMenuItem(); + this.menuStatsFilter = new System.Windows.Forms.ToolStripMenuItem(); + this.menuAllStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuSeasonStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuWeekStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuDayStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuSessionStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuPartyFilter = new System.Windows.Forms.ToolStripMenuItem(); + this.menuAllPartyStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuSoloStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuPartyStats = new System.Windows.Forms.ToolStripMenuItem(); + this.menuProfile = new System.Windows.Forms.ToolStripMenuItem(); + this.menuProfileMain = new System.Windows.Forms.ToolStripMenuItem(); + this.menuProfilePractice = new System.Windows.Forms.ToolStripMenuItem(); + this.menuOverlay = new System.Windows.Forms.ToolStripMenuItem(); + this.menuUpdate = new System.Windows.Forms.ToolStripMenuItem(); + this.menuHelp = new System.Windows.Forms.ToolStripMenuItem(); + this.menuLaunchFallGuys = new System.Windows.Forms.ToolStripMenuItem(); + this.gridDetails = new FallGuysStats.Grid(); + this.infoStrip = new System.Windows.Forms.ToolStrip(); + this.lblTotalTime = new System.Windows.Forms.ToolStripLabel(); + this.lblTotalShows = new System.Windows.Forms.ToolStripLabel(); + this.lblTotalRounds = new System.Windows.Forms.ToolStripLabel(); + this.lblTotalWins = new System.Windows.Forms.ToolStripLabel(); + this.lblTotalFinals = new System.Windows.Forms.ToolStripLabel(); + this.lblKudos = new System.Windows.Forms.ToolStripLabel(); + this.menu.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.gridDetails)).BeginInit(); + this.infoStrip.SuspendLayout(); + this.SuspendLayout(); + // + // menu + // + this.menu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuSettings, + this.menuFilters, + this.menuProfile, + this.menuOverlay, + this.menuUpdate, + this.menuHelp, + this.menuLaunchFallGuys}); + this.menu.Location = new System.Drawing.Point(0, 0); + this.menu.Name = "menu"; + this.menu.Size = new System.Drawing.Size(634, 24); + this.menu.TabIndex = 12; + this.menu.Text = "menuStrip1"; + // + // menuSettings + // + this.menuSettings.Name = "menuSettings"; + this.menuSettings.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); + this.menuSettings.Size = new System.Drawing.Size(61, 20); + this.menuSettings.Text = "Settings"; + this.menuSettings.Click += new System.EventHandler(this.menuSettings_Click); + // + // menuFilters + // + this.menuFilters.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuStatsFilter, + this.menuPartyFilter}); + this.menuFilters.Name = "menuFilters"; + this.menuFilters.Size = new System.Drawing.Size(50, 20); + this.menuFilters.Text = "Filters"; + // + // menuStatsFilter + // + this.menuStatsFilter.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuAllStats, + this.menuSeasonStats, + this.menuWeekStats, + this.menuDayStats, + this.menuSessionStats}); + this.menuStatsFilter.Name = "menuStatsFilter"; + this.menuStatsFilter.Size = new System.Drawing.Size(101, 22); + this.menuStatsFilter.Text = "Stats"; + // + // menuAllStats + // + this.menuAllStats.Checked = true; + this.menuAllStats.CheckOnClick = true; + this.menuAllStats.CheckState = System.Windows.Forms.CheckState.Checked; + this.menuAllStats.Name = "menuAllStats"; + this.menuAllStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.A))); + this.menuAllStats.Size = new System.Drawing.Size(187, 22); + this.menuAllStats.Text = "All"; + this.menuAllStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuSeasonStats + // + this.menuSeasonStats.CheckOnClick = true; + this.menuSeasonStats.Name = "menuSeasonStats"; + this.menuSeasonStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.S))); + this.menuSeasonStats.Size = new System.Drawing.Size(187, 22); + this.menuSeasonStats.Text = "Season"; + this.menuSeasonStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuWeekStats + // + this.menuWeekStats.CheckOnClick = true; + this.menuWeekStats.Name = "menuWeekStats"; + this.menuWeekStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.W))); + this.menuWeekStats.Size = new System.Drawing.Size(187, 22); + this.menuWeekStats.Text = "Week"; + this.menuWeekStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuDayStats + // + this.menuDayStats.CheckOnClick = true; + this.menuDayStats.Name = "menuDayStats"; + this.menuDayStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.D))); + this.menuDayStats.Size = new System.Drawing.Size(187, 22); + this.menuDayStats.Text = "Day"; + this.menuDayStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuSessionStats + // + this.menuSessionStats.CheckOnClick = true; + this.menuSessionStats.Name = "menuSessionStats"; + this.menuSessionStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.G))); + this.menuSessionStats.Size = new System.Drawing.Size(187, 22); + this.menuSessionStats.Text = "Session"; + this.menuSessionStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuPartyFilter + // + this.menuPartyFilter.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuAllPartyStats, + this.menuSoloStats, + this.menuPartyStats}); + this.menuPartyFilter.Name = "menuPartyFilter"; + this.menuPartyFilter.Size = new System.Drawing.Size(101, 22); + this.menuPartyFilter.Text = "Party"; + // + // menuAllPartyStats + // + this.menuAllPartyStats.Checked = true; + this.menuAllPartyStats.CheckOnClick = true; + this.menuAllPartyStats.CheckState = System.Windows.Forms.CheckState.Checked; + this.menuAllPartyStats.Name = "menuAllPartyStats"; + this.menuAllPartyStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.F))); + this.menuAllPartyStats.Size = new System.Drawing.Size(174, 22); + this.menuAllPartyStats.Text = "All"; + this.menuAllPartyStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuSoloStats + // + this.menuSoloStats.CheckOnClick = true; + this.menuSoloStats.Name = "menuSoloStats"; + this.menuSoloStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.O))); + this.menuSoloStats.Size = new System.Drawing.Size(174, 22); + this.menuSoloStats.Text = "Solo"; + this.menuSoloStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuPartyStats + // + this.menuPartyStats.CheckOnClick = true; + this.menuPartyStats.Name = "menuPartyStats"; + this.menuPartyStats.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.P))); + this.menuPartyStats.Size = new System.Drawing.Size(174, 22); + this.menuPartyStats.Text = "Party"; + this.menuPartyStats.Click += new System.EventHandler(this.menuStats_Click); + // + // menuProfile + // + this.menuProfile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuProfileMain, + this.menuProfilePractice}); + this.menuProfile.Name = "menuProfile"; + this.menuProfile.Size = new System.Drawing.Size(53, 20); + this.menuProfile.Text = "Profile"; + // + // menuProfileMain + // + this.menuProfileMain.Checked = true; + this.menuProfileMain.CheckOnClick = true; + this.menuProfileMain.CheckState = System.Windows.Forms.CheckState.Checked; + this.menuProfileMain.Name = "menuProfileMain"; + this.menuProfileMain.Size = new System.Drawing.Size(116, 22); + this.menuProfileMain.Text = "Main"; + this.menuProfileMain.Click += new System.EventHandler(this.menuStats_Click); + // + // menuProfilePractice + // + this.menuProfilePractice.CheckOnClick = true; + this.menuProfilePractice.Name = "menuProfilePractice"; + this.menuProfilePractice.Size = new System.Drawing.Size(116, 22); + this.menuProfilePractice.Text = "Practice"; + this.menuProfilePractice.Click += new System.EventHandler(this.menuStats_Click); + // + // menuOverlay + // + this.menuOverlay.Name = "menuOverlay"; + this.menuOverlay.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); + this.menuOverlay.Size = new System.Drawing.Size(59, 20); + this.menuOverlay.Text = "Overlay"; + this.menuOverlay.Click += new System.EventHandler(this.menuOverlay_Click); + // + // menuUpdate + // + this.menuUpdate.Name = "menuUpdate"; + this.menuUpdate.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.U))); + this.menuUpdate.Size = new System.Drawing.Size(57, 20); + this.menuUpdate.Text = "Update"; + this.menuUpdate.Click += new System.EventHandler(this.menuUpdate_Click); + // + // menuHelp + // + this.menuHelp.Name = "menuHelp"; + this.menuHelp.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.H))); + this.menuHelp.Size = new System.Drawing.Size(44, 20); + this.menuHelp.Text = "Help"; + this.menuHelp.Click += new System.EventHandler(this.menuHelp_Click); + // + // menuLaunchFallGuys + // + this.menuLaunchFallGuys.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.menuLaunchFallGuys.Name = "menuLaunchFallGuys"; + this.menuLaunchFallGuys.Size = new System.Drawing.Size(109, 20); + this.menuLaunchFallGuys.Text = "Launch Fall Guys"; + this.menuLaunchFallGuys.Click += new System.EventHandler(this.menuLaunchFallGuys_Click); + // + // gridDetails + // + this.gridDetails.AllowUserToDeleteRows = false; + this.gridDetails.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gridDetails.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; + this.gridDetails.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.gridDetails.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single; + dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle1.BackColor = System.Drawing.Color.LightGray; + dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + dataGridViewCellStyle1.ForeColor = System.Drawing.Color.Black; + dataGridViewCellStyle1.SelectionBackColor = System.Drawing.Color.Cyan; + dataGridViewCellStyle1.SelectionForeColor = System.Drawing.Color.Black; + dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.gridDetails.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1; + this.gridDetails.ColumnHeadersHeight = 20; + this.gridDetails.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.DisableResizing; + dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle2.BackColor = System.Drawing.Color.White; + dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + dataGridViewCellStyle2.ForeColor = System.Drawing.Color.Black; + dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.DeepSkyBlue; + dataGridViewCellStyle2.SelectionForeColor = System.Drawing.Color.Black; + dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.gridDetails.DefaultCellStyle = dataGridViewCellStyle2; + this.gridDetails.EnableHeadersVisualStyles = false; + this.gridDetails.GridColor = System.Drawing.Color.Gray; + this.gridDetails.Location = new System.Drawing.Point(0, 50); + this.gridDetails.Name = "gridDetails"; + this.gridDetails.ReadOnly = true; + this.gridDetails.RowHeadersVisible = false; + this.gridDetails.Size = new System.Drawing.Size(634, 570); + this.gridDetails.TabIndex = 11; + this.gridDetails.TabStop = false; + this.gridDetails.DataSourceChanged += new System.EventHandler(this.gridDetails_DataSourceChanged); + this.gridDetails.CellClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.gridDetails_CellClick); + this.gridDetails.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.gridDetails_CellFormatting); + this.gridDetails.CellMouseEnter += new System.Windows.Forms.DataGridViewCellEventHandler(this.gridDetails_CellMouseEnter); + this.gridDetails.ColumnHeaderMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.gridDetails_ColumnHeaderMouseClick); + this.gridDetails.SelectionChanged += new System.EventHandler(this.gridDetails_SelectionChanged); + // + // infoStrip + // + this.infoStrip.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(242)))), ((int)(((byte)(251))))); + this.infoStrip.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.infoStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.lblTotalTime, + this.lblTotalShows, + this.lblTotalRounds, + this.lblTotalWins, + this.lblTotalFinals, + this.lblKudos}); + this.infoStrip.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow; + this.infoStrip.Location = new System.Drawing.Point(0, 24); + this.infoStrip.Name = "infoStrip"; + this.infoStrip.Padding = new System.Windows.Forms.Padding(4, 6, 1, 1); + this.infoStrip.Size = new System.Drawing.Size(634, 26); + this.infoStrip.TabIndex = 13; + // + // lblTotalTime + // + this.lblTotalTime.Margin = new System.Windows.Forms.Padding(4, 1, 0, 2); + this.lblTotalTime.Name = "lblTotalTime"; + this.lblTotalTime.Size = new System.Drawing.Size(107, 13); + this.lblTotalTime.Text = "Time Played: 0:00:00"; + // + // lblTotalShows + // + this.lblTotalShows.ForeColor = System.Drawing.Color.Blue; + this.lblTotalShows.Margin = new System.Windows.Forms.Padding(10, 1, 0, 2); + this.lblTotalShows.Name = "lblTotalShows"; + this.lblTotalShows.Size = new System.Drawing.Size(51, 13); + this.lblTotalShows.Text = "Shows: 0"; + this.lblTotalShows.Click += new System.EventHandler(this.lblTotalShows_Click); + this.lblTotalShows.MouseEnter += new System.EventHandler(this.infoStrip_MouseEnter); + this.lblTotalShows.MouseLeave += new System.EventHandler(this.infoStrip_MouseLeave); + // + // lblTotalRounds + // + this.lblTotalRounds.ForeColor = System.Drawing.Color.Blue; + this.lblTotalRounds.Margin = new System.Windows.Forms.Padding(10, 1, 0, 2); + this.lblTotalRounds.Name = "lblTotalRounds"; + this.lblTotalRounds.Size = new System.Drawing.Size(56, 13); + this.lblTotalRounds.Text = "Rounds: 0"; + this.lblTotalRounds.Click += new System.EventHandler(this.lblTotalRounds_Click); + this.lblTotalRounds.MouseEnter += new System.EventHandler(this.infoStrip_MouseEnter); + this.lblTotalRounds.MouseLeave += new System.EventHandler(this.infoStrip_MouseLeave); + // + // lblTotalWins + // + this.lblTotalWins.ForeColor = System.Drawing.Color.Blue; + this.lblTotalWins.Margin = new System.Windows.Forms.Padding(10, 1, 0, 2); + this.lblTotalWins.Name = "lblTotalWins"; + this.lblTotalWins.Size = new System.Drawing.Size(78, 13); + this.lblTotalWins.Text = "Wins: 0 (0.0 %)"; + this.lblTotalWins.Click += new System.EventHandler(this.lblTotalWins_Click); + this.lblTotalWins.MouseEnter += new System.EventHandler(this.infoStrip_MouseEnter); + this.lblTotalWins.MouseLeave += new System.EventHandler(this.infoStrip_MouseLeave); + // + // lblTotalFinals + // + this.lblTotalFinals.ForeColor = System.Drawing.Color.Blue; + this.lblTotalFinals.Margin = new System.Windows.Forms.Padding(10, 1, 0, 2); + this.lblTotalFinals.Name = "lblTotalFinals"; + this.lblTotalFinals.Size = new System.Drawing.Size(81, 13); + this.lblTotalFinals.Text = "Finals: 0 (0.0 %)"; + this.lblTotalFinals.Click += new System.EventHandler(this.lblTotalFinals_Click); + this.lblTotalFinals.MouseEnter += new System.EventHandler(this.infoStrip_MouseEnter); + this.lblTotalFinals.MouseLeave += new System.EventHandler(this.infoStrip_MouseLeave); + // + // lblKudos + // + this.lblKudos.Margin = new System.Windows.Forms.Padding(10, 1, 0, 2); + this.lblKudos.Name = "lblKudos"; + this.lblKudos.Size = new System.Drawing.Size(49, 13); + this.lblKudos.Text = "Kudos: 0"; + // + // Stats + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(242)))), ((int)(((byte)(251))))); + this.ClientSize = new System.Drawing.Size(634, 620); + this.Controls.Add(this.infoStrip); + this.Controls.Add(this.gridDetails); + this.Controls.Add(this.menu); + this.DoubleBuffered = true; + this.ForeColor = System.Drawing.Color.Black; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MainMenuStrip = this.menu; + this.MinimumSize = new System.Drawing.Size(650, 300); + this.Name = "Stats"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Fall Guys Stats"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Stats_FormClosing); + this.Load += new System.EventHandler(this.Stats_Load); + this.Shown += new System.EventHandler(this.Stats_Shown); + this.menu.ResumeLayout(false); + this.menu.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.gridDetails)).EndInit(); + this.infoStrip.ResumeLayout(false); + this.infoStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private Grid gridDetails; + private System.Windows.Forms.MenuStrip menu; + private System.Windows.Forms.ToolStripMenuItem menuSettings; + private System.Windows.Forms.ToolStripMenuItem menuFilters; + private System.Windows.Forms.ToolStripMenuItem menuStatsFilter; + private System.Windows.Forms.ToolStripMenuItem menuAllStats; + private System.Windows.Forms.ToolStripMenuItem menuSeasonStats; + private System.Windows.Forms.ToolStripMenuItem menuWeekStats; + private System.Windows.Forms.ToolStripMenuItem menuDayStats; + private System.Windows.Forms.ToolStripMenuItem menuSessionStats; + private System.Windows.Forms.ToolStripMenuItem menuPartyFilter; + private System.Windows.Forms.ToolStripMenuItem menuAllPartyStats; + private System.Windows.Forms.ToolStripMenuItem menuSoloStats; + private System.Windows.Forms.ToolStripMenuItem menuPartyStats; + private System.Windows.Forms.ToolStripMenuItem menuOverlay; + private System.Windows.Forms.ToolStripMenuItem menuUpdate; + private System.Windows.Forms.ToolStripMenuItem menuHelp; + private System.Windows.Forms.ToolStripMenuItem menuProfile; + private System.Windows.Forms.ToolStripMenuItem menuProfileMain; + private System.Windows.Forms.ToolStripMenuItem menuProfilePractice; + private System.Windows.Forms.ToolStrip infoStrip; + private System.Windows.Forms.ToolStripLabel lblTotalTime; + private System.Windows.Forms.ToolStripLabel lblTotalShows; + private System.Windows.Forms.ToolStripLabel lblTotalRounds; + private System.Windows.Forms.ToolStripLabel lblTotalWins; + private System.Windows.Forms.ToolStripLabel lblTotalFinals; + private System.Windows.Forms.ToolStripLabel lblKudos; + private System.Windows.Forms.ToolStripMenuItem menuLaunchFallGuys; + } +} + diff --git a/Views/Stats.cs b/Views/Stats.cs new file mode 100644 index 000000000..b0e6032a9 --- /dev/null +++ b/Views/Stats.cs @@ -0,0 +1,1471 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Windows.Forms; +using LiteDB; +using Microsoft.Win32; +namespace FallGuysStats { + public partial class Stats : Form { + [STAThread] + static void Main(string[] args) { + try { +#if AllowUpdate + foreach (string file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.old")) { + int retries = 0; + while (retries < 20) { + try { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + break; + } catch { + retries++; + } + Thread.Sleep(50); + } + } +#endif + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Stats()); + } catch (Exception ex) { + MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private static string LOGNAME = "Player.log"; + private static List Seasons = new List { + new DateTime(2020, 8, 4, 0, 0, 0, DateTimeKind.Utc), + new DateTime(2020, 10, 8, 0, 0, 0, DateTimeKind.Utc), + new DateTime(2020, 12, 15, 0, 0, 0, DateTimeKind.Utc) + }; + private static DateTime SeasonStart, WeekStart, DayStart; + private static DateTime SessionStart = DateTime.UtcNow; + public static bool InShow = false; + public static bool EndedShow = false; + public static int LastServerPing = 0; + + public List StatDetails = new List(); + public List CurrentRound = null; + public List AllStats = new List(); + public Dictionary StatLookup = new Dictionary(); + private LogFileWatcher logFile = new LogFileWatcher(); + public int Shows; + public int Rounds; + public TimeSpan Duration; + public int Wins; + public int Finals; + public int Kudos; + private int nextShowID; + private bool loadingExisting; + public LiteDatabase StatsDB; + public ILiteCollection RoundDetails; + public ILiteCollection UserSettings; + public UserSettings CurrentSettings; + private Overlay overlay; + private DateTime lastAddedShow = DateTime.MinValue; + private DateTime startupTime = DateTime.UtcNow; + private int askedPreviousShows = 0; + private TextInfo textInfo; + public Stats() { + InitializeComponent(); + + Text = $"Fall Guys Stats v{Assembly.GetExecutingAssembly().GetName().Version.ToString(2)}"; + textInfo = Thread.CurrentThread.CurrentCulture.TextInfo; + + logFile.OnParsedLogLines += LogFile_OnParsedLogLines; + logFile.OnNewLogFileDate += LogFile_OnNewLogFileDate; + logFile.OnError += LogFile_OnError; + logFile.OnParsedLogLinesCurrent += LogFile_OnParsedLogLinesCurrent; + + foreach (var entry in LevelStats.ALL) { + StatDetails.Add(entry.Value); + StatLookup.Add(entry.Key, entry.Value); + } + + gridDetails.DataSource = StatDetails; + + StatsDB = new LiteDatabase(@"data.db"); + StatsDB.Pragma("UTC_DATE", true); + RoundDetails = StatsDB.GetCollection("RoundDetails"); + UserSettings = StatsDB.GetCollection("UserSettings"); + + StatsDB.BeginTrans(); + if (UserSettings.Count() == 0) { + CurrentSettings = GetDefaultSettings(); + UserSettings.Insert(CurrentSettings); + } else { + try { + CurrentSettings = UserSettings.FindAll().First(); + } catch { + UserSettings.DeleteAll(); + CurrentSettings = GetDefaultSettings(); + UserSettings.Insert(CurrentSettings); + } + } + + UpdateHoopsieLegends(); + + RoundDetails.EnsureIndex(x => x.Name); + RoundDetails.EnsureIndex(x => x.ShowID); + RoundDetails.EnsureIndex(x => x.Round); + RoundDetails.EnsureIndex(x => x.Start); + RoundDetails.EnsureIndex(x => x.InParty); + StatsDB.Commit(); + + UpdateDatabaseVersion(); + + CurrentRound = new List(); + + overlay = new Overlay() { StatsForm = this, Icon = Icon, ShowIcon = true }; + overlay.Show(); + overlay.Visible = false; + overlay.StartTimer(); + + UpdateGameExeLocation(); + if (CurrentSettings.AutoLaunchGameOnStartup) { + LaunchGame(true); + } + } + private void UpdateDatabaseVersion() { + if (!CurrentSettings.UpdatedDateFormat) { + AllStats.AddRange(RoundDetails.FindAll()); + StatsDB.BeginTrans(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + info.Start = DateTime.SpecifyKind(info.Start.ToLocalTime(), DateTimeKind.Utc); + info.End = DateTime.SpecifyKind(info.End.ToLocalTime(), DateTimeKind.Utc); + info.Finish = info.Finish.HasValue ? DateTime.SpecifyKind(info.Finish.Value.ToLocalTime(), DateTimeKind.Utc) : (DateTime?)null; + RoundDetails.Update(info); + } + StatsDB.Commit(); + AllStats.Clear(); + CurrentSettings.UpdatedDateFormat = true; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 0) { + CurrentSettings.SwitchBetweenQualify = CurrentSettings.SwitchBetweenLongest; + CurrentSettings.Version = 1; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 1) { + CurrentSettings.SwitchBetweenPlayers = CurrentSettings.SwitchBetweenLongest; + CurrentSettings.Version = 2; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 2) { + CurrentSettings.SwitchBetweenStreaks = CurrentSettings.SwitchBetweenLongest; + CurrentSettings.Version = 3; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 4) { + AllStats.AddRange(RoundDetails.FindAll()); + StatsDB.BeginTrans(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + int index = 0; + if ((index = info.Name.IndexOf("_variation", StringComparison.OrdinalIgnoreCase)) > 0) { + info.Name = info.Name.Substring(0, index); + RoundDetails.Update(info); + } + } + StatsDB.Commit(); + AllStats.Clear(); + CurrentSettings.Version = 5; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 5 || CurrentSettings.Version == 6) { + AllStats.AddRange(RoundDetails.FindAll()); + StatsDB.BeginTrans(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + int index = 0; + if ((index = info.Name.IndexOf("_northernlion", StringComparison.OrdinalIgnoreCase)) > 0) { + info.Name = info.Name.Substring(0, index); + RoundDetails.Update(info); + } + } + StatsDB.Commit(); + AllStats.Clear(); + CurrentSettings.Version = 7; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 7) { + AllStats.AddRange(RoundDetails.FindAll()); + StatsDB.BeginTrans(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + int index = 0; + if ((index = info.Name.IndexOf("_hard_mode", StringComparison.OrdinalIgnoreCase)) > 0) { + info.Name = info.Name.Substring(0, index); + RoundDetails.Update(info); + } + } + StatsDB.Commit(); + AllStats.Clear(); + CurrentSettings.Version = 8; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 8) { + AllStats.AddRange(RoundDetails.FindAll()); + StatsDB.BeginTrans(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + int index = 0; + if ((index = info.Name.IndexOf("_event_", StringComparison.OrdinalIgnoreCase)) > 0) { + info.Name = info.Name.Substring(0, index); + RoundDetails.Update(info); + } + } + StatsDB.Commit(); + AllStats.Clear(); + CurrentSettings.Version = 9; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 9) { + AllStats.AddRange(RoundDetails.FindAll()); + StatsDB.BeginTrans(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + if (info.Name.Equals("round_fall_mountain", StringComparison.OrdinalIgnoreCase)) { + info.Name = "round_fall_mountain_hub_complete"; + RoundDetails.Update(info); + } + } + StatsDB.Commit(); + AllStats.Clear(); + CurrentSettings.Version = 10; + SaveUserSettings(); + } + + if (CurrentSettings.Version == 10) { + AllStats.AddRange(RoundDetails.FindAll()); + StatsDB.BeginTrans(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + int index = 0; + if ((index = info.Name.IndexOf("_event_", StringComparison.OrdinalIgnoreCase)) > 0 + || (index = info.Name.IndexOf(". D", StringComparison.OrdinalIgnoreCase)) > 0) { + info.Name = info.Name.Substring(0, index); + RoundDetails.Update(info); + } + } + StatsDB.Commit(); + AllStats.Clear(); + CurrentSettings.Version = 11; + SaveUserSettings(); + } + } + private UserSettings GetDefaultSettings() { + return new UserSettings() { + ID = 1, + CycleTimeSeconds = 5, + FilterType = 0, + SelectedProfile = 0, + FlippedDisplay = false, + LogPath = null, + OverlayColor = 3, + OverlayLocationX = null, + OverlayLocationY = null, + SwitchBetweenLongest = true, + SwitchBetweenQualify = true, + SwitchBetweenPlayers = true, + SwitchBetweenStreaks = true, + OnlyShowLongest = false, + OnlyShowGold = false, + OnlyShowPing = false, + OnlyShowFinalStreak = false, + OverlayVisible = false, + OverlayNotOnTop = false, + UseNDI = false, + PreviousWins = 0, + WinsFilter = 0, + QualifyFilter = 0, + FastestFilter = 0, + HideWinsInfo = false, + HideRoundInfo = false, + HideTimeInfo = false, + ShowOverlayTabs = false, + ShowPercentages = false, + AutoUpdate = false, + FormLocationX = null, + FormLocationY = null, + FormWidth = null, + FormHeight = null, + OverlayWidth = 786, + OverlayHeight = 99, + HideOverlayPercentages = false, + HoopsieHeros = false, + Version = 11, + AutoLaunchGameOnStartup = false, + GameExeLocation = string.Empty, + IgnoreLevelTypeWhenSorting = false, + UpdatedDateFormat = true + }; + } + private void UpdateHoopsieLegends() { + LevelStats level = StatLookup["round_hoops_blockade_solo"]; + string newName = CurrentSettings.HoopsieHeros ? "Hoopsie Heroes" : "Hoopsie Legends"; + if (level.Name != newName) { + level.Name = newName; + gridDetails.Invalidate(); + } + } + public void UpdateDates() { + if (DateTime.Now.Date.ToUniversalTime() == DayStart) { return; } + + DateTime currentUTC = DateTime.UtcNow; + for (int i = Seasons.Count - 1; i >= 0; i--) { + if (currentUTC > Seasons[i]) { + SeasonStart = Seasons[i]; + break; + } + } + WeekStart = DateTime.Now.Date.AddDays(-7).ToUniversalTime(); + DayStart = DateTime.Now.Date.ToUniversalTime(); + + ResetStats(); + } + public void SaveUserSettings() { + lock (StatsDB) { + StatsDB.BeginTrans(); + UserSettings.Update(CurrentSettings); + StatsDB.Commit(); + } + } + public void ResetStats() { + for (int i = 0; i < StatDetails.Count; i++) { + LevelStats calculator = StatDetails[i]; + calculator.Clear(); + } + + ClearTotals(); + + List rounds = new List(); + int profile = menuProfileMain.Checked ? 0 : 1; + + lock (StatsDB) { + AllStats.Clear(); + nextShowID = 0; + lastAddedShow = DateTime.MinValue; + if (RoundDetails.Count() > 0) { + AllStats.AddRange(RoundDetails.FindAll()); + AllStats.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + + if (AllStats.Count > 0) { + nextShowID = AllStats[AllStats.Count - 1].ShowID; + + int lastAddedShowID = -1; + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + info.ToLocalTime(); + if (info.Profile != profile) { continue; } + + if (info.ShowID == lastAddedShowID || (IsInStatsFilter(info.Start) && IsInPartyFilter(info))) { + lastAddedShowID = info.ShowID; + rounds.Add(info); + } + + if (info.Start > lastAddedShow && info.Round == 1) { + lastAddedShow = info.Start; + } + } + } + } + } + + lock (CurrentRound) { + CurrentRound.Clear(); + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo info = AllStats[i]; + if (info.Profile != profile) { continue; } + + CurrentRound.Insert(0, info); + if (info.Round == 1) { + break; + } + } + } + + loadingExisting = true; + LogFile_OnParsedLogLines(rounds); + loadingExisting = false; + } + private void Stats_FormClosing(object sender, FormClosingEventArgs e) { + try { + if (!overlay.Disposing && !overlay.IsDisposed && !IsDisposed && !Disposing) { + if (overlay.Visible) { + CurrentSettings.OverlayLocationX = overlay.Location.X; + CurrentSettings.OverlayLocationY = overlay.Location.Y; + + CurrentSettings.OverlayWidth = overlay.Width; + CurrentSettings.OverlayHeight = overlay.Height; + } + CurrentSettings.FilterType = menuAllStats.Checked ? 0 : menuSeasonStats.Checked ? 1 : menuWeekStats.Checked ? 2 : menuDayStats.Checked ? 3 : 4; + CurrentSettings.SelectedProfile = menuProfileMain.Checked ? 0 : 1; + + CurrentSettings.FormLocationX = Location.X; + CurrentSettings.FormLocationY = Location.Y; + CurrentSettings.FormWidth = ClientSize.Width; + CurrentSettings.FormHeight = ClientSize.Height; + SaveUserSettings(); + } + StatsDB.Dispose(); + overlay.Cleanup(); + } catch { } + } + private void Stats_Load(object sender, EventArgs e) { + try { + if (CurrentSettings.FormWidth.HasValue) { + ClientSize = new Size(CurrentSettings.FormWidth.Value, CurrentSettings.FormHeight.Value); + } + if (CurrentSettings.FormLocationX.HasValue && IsOnScreen(CurrentSettings.FormLocationX.Value, CurrentSettings.FormLocationY.Value, Width)) { + Location = new Point(CurrentSettings.FormLocationX.Value, CurrentSettings.FormLocationY.Value); + } + +#if AllowUpdate + if (CurrentSettings.AutoUpdate && CheckForUpdate(true)) { + return; + } +#endif + + if (CurrentSettings.SelectedProfile != 0) { + menuProfileMain.Checked = false; + switch (CurrentSettings.SelectedProfile) { + case 1: menuProfilePractice.Checked = true; break; + } + } + + UpdateDates(); + } catch { } + } + private void Stats_Shown(object sender, EventArgs e) { + try { + string logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "Low", "Mediatonic", "FallGuys_client"); + if (!string.IsNullOrEmpty(CurrentSettings.LogPath)) { + logPath = CurrentSettings.LogPath; + } + logFile.Start(logPath, LOGNAME); + + overlay.ArrangeDisplay(CurrentSettings.FlippedDisplay, CurrentSettings.ShowOverlayTabs, CurrentSettings.HideWinsInfo, CurrentSettings.HideRoundInfo, CurrentSettings.HideTimeInfo, CurrentSettings.OverlayColor, + (int)(CurrentSettings.OverlayWidth * CurrentSettings.OverlayScale), (int)(CurrentSettings.OverlayHeight * CurrentSettings.OverlayScale), CurrentSettings.OverlayFontSerialized); + if (CurrentSettings.OverlayVisible) { + ToggleOverlay(overlay); + } + + menuAllStats.Checked = false; + switch (CurrentSettings.FilterType) { + case 0: menuAllStats.Checked = true; menuStats_Click(menuAllStats, null); break; + case 1: menuSeasonStats.Checked = true; menuStats_Click(menuSeasonStats, null); break; + case 2: menuWeekStats.Checked = true; menuStats_Click(menuWeekStats, null); break; + case 3: menuDayStats.Checked = true; menuStats_Click(menuDayStats, null); break; + case 4: menuSessionStats.Checked = true; menuStats_Click(menuSessionStats, null); break; + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void LogFile_OnError(string error) { + if (!Disposing && !IsDisposed) { + try { + if (InvokeRequired) { + Invoke((Action)LogFile_OnError, error); + } else { + MessageBox.Show(this, error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } catch { } + } + } + private void LogFile_OnNewLogFileDate(DateTime newDate) { + if (SessionStart != newDate) { + SessionStart = newDate; + if (menuSessionStats.Checked) { + menuStats_Click(menuSessionStats, null); + } + } + } + private void LogFile_OnParsedLogLinesCurrent(List round) { + lock (CurrentRound) { + if (CurrentRound == null || CurrentRound.Count != round.Count) { + CurrentRound = round; + } else { + for (int i = 0; i < CurrentRound.Count; i++) { + RoundInfo info = CurrentRound[i]; + if (!info.Equals(round[i])) { + CurrentRound = round; + break; + } + } + } + } + } + private void LogFile_OnParsedLogLines(List round) { + try { + if (InvokeRequired) { + Invoke((Action>)LogFile_OnParsedLogLines, round); + return; + } + + lock (StatsDB) { + if (!loadingExisting) { StatsDB.BeginTrans(); } + + int profile = menuProfileMain.Checked ? 0 : 1; + foreach (RoundInfo stat in round) { + if (!loadingExisting) { + RoundInfo info = null; + for (int i = AllStats.Count - 1; i >= 0; i--) { + RoundInfo temp = AllStats[i]; + if (temp.Start == stat.Start && temp.Name == stat.Name) { + info = temp; + break; + } + } + + if (info == null && stat.Start > lastAddedShow) { + if (stat.ShowEnd < startupTime && askedPreviousShows == 0) { + if (MessageBox.Show(this, "There are previous shows not in your current stats. Do you wish to add these to your stats?", "Previous Shows", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { + askedPreviousShows = 1; + } else { + askedPreviousShows = 2; + } + } + + if (stat.ShowEnd < startupTime && askedPreviousShows == 2) { + continue; + } + + if (stat.Round == 1) { + nextShowID++; + lastAddedShow = stat.Start; + } + stat.ShowID = nextShowID; + stat.Profile = profile; + RoundDetails.Insert(stat); + AllStats.Add(stat); + } else { + continue; + } + } + + if (!stat.PrivateLobby) { + if (stat.Round == 1) { + Shows++; + } + Rounds++; + } + Duration += stat.End - stat.Start; + Kudos += stat.Kudos; + + // add new type of round to the rounds lookup + if (!StatLookup.ContainsKey(stat.Name)) { + string roundName = stat.Name; + if (roundName.StartsWith("round_", StringComparison.OrdinalIgnoreCase)) { + roundName = roundName.Substring(6).Replace('_', ' '); + } + + LevelStats newLevel = new LevelStats(textInfo.ToTitleCase(roundName), LevelType.Unknown, false, 0); + StatLookup.Add(stat.Name, newLevel); + StatDetails.Add(newLevel); + gridDetails.DataSource = null; + gridDetails.DataSource = StatDetails; + } + + stat.ToLocalTime(); + LevelStats levelStats = StatLookup[stat.Name]; + if (!stat.PrivateLobby) { + if (levelStats.IsFinal || stat.Crown) { + Finals++; + if (stat.Qualified) { + Wins++; + } + } + } + levelStats.Add(stat); + } + + if (!loadingExisting) { StatsDB.Commit(); } + } + + lock (CurrentRound) { + CurrentRound.Clear(); + for (int i = round.Count - 1; i >= 0; i--) { + RoundInfo info = round[i]; + CurrentRound.Insert(0, info); + if (info.Round == 1) { + break; + } + } + } + + if (!Disposing && !IsDisposed) { + try { + UpdateTotals(); + } catch { } + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private bool IsInStatsFilter(DateTime showEnd) { + return menuAllStats.Checked || + (menuSeasonStats.Checked && showEnd > SeasonStart) || + (menuWeekStats.Checked && showEnd > WeekStart) || + (menuDayStats.Checked && showEnd > DayStart) || + (menuSessionStats.Checked && showEnd > SessionStart); + } + private bool IsInPartyFilter(RoundInfo info) { + return menuAllPartyStats.Checked || + (menuSoloStats.Checked && !info.InParty) || + (menuPartyStats.Checked && info.InParty); + } + public string GetCurrentFilter() { + return menuAllStats.Checked ? "ALL TIME" : menuSeasonStats.Checked ? "SEASON" : menuWeekStats.Checked ? "WEEK" : menuDayStats.Checked ? "DAY" : "SESSION"; + } + public StatSummary GetLevelInfo(string name) { + StatSummary summary = new StatSummary(); + LevelStats levelDetails; + + summary.AllWins = 0; + summary.TotalShows = 0; + summary.TotalPlays = 0; + summary.TotalWins = 0; + summary.TotalFinals = 0; + int lastShow = -1; + LevelStats currentLevel; + if (!StatLookup.TryGetValue(name ?? string.Empty, out currentLevel)) { + currentLevel = new LevelStats(name, LevelType.Unknown, false, 0); + } + int profile = menuProfileMain.Checked ? 0 : 1; + + for (int i = 0; i < AllStats.Count; i++) { + RoundInfo info = AllStats[i]; + if (info.Profile != profile) { continue; } + + TimeSpan finishTime = info.Finish.GetValueOrDefault(info.End) - info.Start; + bool hasLevelDetails = StatLookup.TryGetValue(info.Name, out levelDetails); + bool isCurrentLevel = currentLevel.Name.Equals(hasLevelDetails ? levelDetails.Name : info.Name, StringComparison.OrdinalIgnoreCase); + + int currentShow = info.ShowID; + RoundInfo endShow = info; + for (int j = i + 1; j < AllStats.Count; j++) { + if (AllStats[j].ShowID != currentShow) { + break; + } + endShow = AllStats[j]; + } + + bool isInQualifyFilter = !endShow.PrivateLobby && (CurrentSettings.QualifyFilter == 0 || + (CurrentSettings.QualifyFilter == 1 && IsInStatsFilter(endShow.Start) && IsInPartyFilter(info)) || + (CurrentSettings.QualifyFilter == 2 && endShow.Start > SeasonStart && IsInPartyFilter(info)) || + (CurrentSettings.QualifyFilter == 3 && endShow.Start > WeekStart && IsInPartyFilter(info)) || + (CurrentSettings.QualifyFilter == 4 && endShow.Start > DayStart && IsInPartyFilter(info)) || + (CurrentSettings.QualifyFilter == 5 && endShow.Start > SessionStart && IsInPartyFilter(info))); + bool isInFastestFilter = CurrentSettings.FastestFilter == 0 || + (CurrentSettings.FastestFilter == 1 && IsInStatsFilter(endShow.Start) && IsInPartyFilter(info)) || + (CurrentSettings.FastestFilter == 2 && endShow.Start > SeasonStart && IsInPartyFilter(info)) || + (CurrentSettings.FastestFilter == 3 && endShow.Start > WeekStart && IsInPartyFilter(info)) || + (CurrentSettings.FastestFilter == 4 && endShow.Start > DayStart && IsInPartyFilter(info)) || + (CurrentSettings.FastestFilter == 5 && endShow.Start > SessionStart && IsInPartyFilter(info)); + bool isInWinsFilter = !endShow.PrivateLobby && (CurrentSettings.WinsFilter == 3 || + (CurrentSettings.WinsFilter == 0 && IsInStatsFilter(endShow.Start) && IsInPartyFilter(info)) || + (CurrentSettings.WinsFilter == 1 && endShow.Start > SeasonStart && IsInPartyFilter(info)) || + (CurrentSettings.WinsFilter == 2 && endShow.Start > WeekStart && IsInPartyFilter(info)) || + (CurrentSettings.WinsFilter == 4 && endShow.Start > DayStart && IsInPartyFilter(info)) || + (CurrentSettings.WinsFilter == 5 && endShow.Start > SessionStart && IsInPartyFilter(info))); + + if (info.ShowID != lastShow) { + lastShow = info.ShowID; + if (isInWinsFilter) { + summary.TotalShows++; + } + } + + if (isCurrentLevel) { + if (isInQualifyFilter) { + summary.TotalPlays++; + } + + if (isInFastestFilter) { + if ((!hasLevelDetails || levelDetails.Type == LevelType.Team) && info.Score.HasValue && (!summary.BestScore.HasValue || info.Score.Value > summary.BestScore.Value)) { + summary.BestScore = info.Score; + } + } + } + + if (levelDetails.IsFinal && !endShow.PrivateLobby) { + summary.CurrentFinalStreak++; + if (summary.BestFinalStreak < summary.CurrentFinalStreak) { + summary.BestFinalStreak = summary.CurrentFinalStreak; + } + } + + if (info.Qualified) { + if ((hasLevelDetails && levelDetails.IsFinal) || info.Crown) { + if (!info.PrivateLobby) { + summary.AllWins++; + } + + if (isInWinsFilter) { + summary.TotalWins++; + summary.TotalFinals++; + } + + if (!info.PrivateLobby) { + summary.CurrentStreak++; + if (summary.CurrentStreak > summary.BestStreak) { + summary.BestStreak = summary.CurrentStreak; + } + } + } + + if (isCurrentLevel) { + if (isInQualifyFilter) { + if (info.Tier == (int)QualifyTier.Gold) { + summary.TotalGolds++; + } + summary.TotalQualify++; + } + + if (isInFastestFilter) { + if (finishTime.TotalSeconds > 1.1 && (!summary.BestFinish.HasValue || summary.BestFinish.Value > finishTime)) { + summary.BestFinish = finishTime; + } + if (finishTime.TotalSeconds > 1.1 && info.Finish.HasValue && (!summary.LongestFinish.HasValue || summary.LongestFinish.Value < finishTime)) { + summary.LongestFinish = finishTime; + } + } + + if (finishTime.TotalSeconds > 1.1 && (!summary.BestFinishOverall.HasValue || summary.BestFinishOverall.Value > finishTime)) { + summary.BestFinishOverall = finishTime; + } + if (finishTime.TotalSeconds > 1.1 && info.Finish.HasValue && (!summary.LongestFinishOverall.HasValue || summary.LongestFinishOverall.Value < finishTime)) { + summary.LongestFinishOverall = finishTime; + } + } + } else if (!info.PrivateLobby) { + if (!levelDetails.IsFinal) { + summary.CurrentFinalStreak = 0; + } + summary.CurrentStreak = 0; + if (isInWinsFilter && hasLevelDetails && levelDetails.IsFinal) { + summary.TotalFinals++; + } + } + } + + return summary; + } + private void ClearTotals() { + Rounds = 0; + Duration = TimeSpan.Zero; + Wins = 0; + Shows = 0; + Finals = 0; + Kudos = 0; + } + private void UpdateTotals() { + try { + lblTotalRounds.Text = $"Rounds: {Rounds}"; + lblTotalShows.Text = $"Shows: {Shows}"; + lblTotalTime.Text = $"Time Played: {(int)Duration.TotalHours}:{Duration:mm\\:ss}"; + float winChance = (float)Wins * 100 / (Shows == 0 ? 1 : Shows); + lblTotalWins.Text = $"Wins: {Wins} ({winChance:0.0} %)"; + float finalChance = (float)Finals * 100 / (Shows == 0 ? 1 : Shows); + lblTotalFinals.Text = $"Finals: {Finals} ({finalChance:0.0} %)"; + lblKudos.Text = $"Kudos: {Kudos}"; + gridDetails.Refresh(); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void gridDetails_DataSourceChanged(object sender, EventArgs e) { + try { + if (gridDetails.Columns.Count == 0) { return; } + int pos = 0; + + gridDetails.Columns["AveKudos"].Visible = false; + gridDetails.Columns["AveDuration"].Visible = false; + gridDetails.Setup("Name", pos++, 0, "Level Name", DataGridViewContentAlignment.MiddleLeft); + gridDetails.Setup("Played", pos++, 55, "Played", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Qualified", pos++, 65, "Qualified", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Gold", pos++, 50, "Gold", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Silver", pos++, 50, "Silver", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Bronze", pos++, 50, "Bronze", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Kudos", pos++, 60, "Kudos", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Fastest", pos++, 60, "Fastest", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("Longest", pos++, 60, "Longest", DataGridViewContentAlignment.MiddleRight); + gridDetails.Setup("AveFinish", pos++, 60, "Average", DataGridViewContentAlignment.MiddleRight); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void gridDetails_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { + try { + if (e.RowIndex < 0) { return; } + + LevelStats info = gridDetails.Rows[e.RowIndex].DataBoundItem as LevelStats; + + switch (gridDetails.Columns[e.ColumnIndex].Name) { + case "Name": + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = "Click to view level stats"; + if (info.IsFinal) { + e.CellStyle.BackColor = Color.Pink; + break; + } + switch (info.Type) { + case LevelType.Race: e.CellStyle.BackColor = Color.LightGoldenrodYellow; break; + case LevelType.Survival: e.CellStyle.BackColor = Color.LightBlue; break; + case LevelType.Team: e.CellStyle.BackColor = Color.LightGreen; break; + case LevelType.Hunt: e.CellStyle.BackColor = Color.LightGoldenrodYellow; break; + case LevelType.Unknown: e.CellStyle.BackColor = Color.LightGray; break; + } + break; + case "Qualified": { + float qualifyChance = info.Qualified * 100f / (info.Played == 0 ? 1 : info.Played); + if (CurrentSettings.ShowPercentages) { + e.Value = $"{qualifyChance:0.0}%"; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{info.Qualified}"; + } else { + e.Value = info.Qualified; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{qualifyChance:0.0}%"; + } + break; + } + case "Gold": { + float qualifyChance = info.Gold * 100f / (info.Played == 0 ? 1 : info.Played); + if (CurrentSettings.ShowPercentages) { + e.Value = $"{qualifyChance:0.0}%"; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{info.Gold}"; + } else { + e.Value = info.Gold; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{qualifyChance:0.0}%"; + } + break; + } + case "Silver": { + float qualifyChance = info.Silver * 100f / (info.Played == 0 ? 1 : info.Played); + if (CurrentSettings.ShowPercentages) { + e.Value = $"{qualifyChance:0.0}%"; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{info.Silver}"; + } else { + e.Value = info.Silver; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{qualifyChance:0.0}%"; + } + break; + } + case "Bronze": { + float qualifyChance = (float)info.Bronze * 100f / (info.Played == 0 ? 1 : info.Played); + if (CurrentSettings.ShowPercentages) { + e.Value = $"{qualifyChance:0.0}%"; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{info.Bronze}"; + } else { + e.Value = info.Bronze; + gridDetails.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = $"{qualifyChance:0.0}%"; + } + break; + } + case "AveFinish": + e.Value = info.AveFinish.ToString("m\\:ss\\.ff"); + break; + case "Fastest": + e.Value = info.Fastest.ToString("m\\:ss\\.ff"); + break; + case "Longest": + e.Value = info.Longest.ToString("m\\:ss\\.ff"); + break; + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void gridDetails_CellMouseEnter(object sender, DataGridViewCellEventArgs e) { + try { + if (e.RowIndex >= 0 && gridDetails.Columns[e.ColumnIndex].Name == "Name") { + gridDetails.Cursor = Cursors.Hand; + } else { + gridDetails.Cursor = Cursors.Default; + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void gridDetails_CellClick(object sender, DataGridViewCellEventArgs e) { + try { + if (e.RowIndex < 0) { return; } + + if (gridDetails.Columns[e.ColumnIndex].Name == "Name") { + using (LevelDetails levelDetails = new LevelDetails()) { + LevelStats stats = gridDetails.Rows[e.RowIndex].DataBoundItem as LevelStats; + levelDetails.LevelName = stats.Name; + List rounds = stats.Stats; + rounds.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + levelDetails.RoundDetails = rounds; + levelDetails.StatsForm = this; + levelDetails.ShowDialog(this); + } + } else { + ToggleWinPercentageDisplay(); + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void gridDetails_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) { + string columnName = gridDetails.Columns[e.ColumnIndex].Name; + SortOrder sortOrder = gridDetails.GetSortOrder(columnName); + + StatDetails.Sort(delegate (LevelStats one, LevelStats two) { + LevelType oneType = one.IsFinal ? LevelType.Hunt : one.Type == LevelType.Hunt ? LevelType.Race : one.Type; + LevelType twoType = two.IsFinal ? LevelType.Hunt : two.Type == LevelType.Hunt ? LevelType.Race : two.Type; + + int typeCompare = CurrentSettings.IgnoreLevelTypeWhenSorting && sortOrder != SortOrder.None ? 0 : ((int)oneType).CompareTo((int)twoType); + + if (sortOrder == SortOrder.Descending) { + LevelStats temp = one; + one = two; + two = temp; + } + + int nameCompare = one.Name.CompareTo(two.Name); + bool percents = CurrentSettings.ShowPercentages; + if (typeCompare == 0 && sortOrder != SortOrder.None) { + switch (columnName) { + case "Gold": typeCompare = ((double)one.Gold / (one.Played > 0 && percents ? one.Played : 1)).CompareTo((double)two.Gold / (two.Played > 0 && percents ? two.Played : 1)); break; + case "Silver": typeCompare = ((double)one.Silver / (one.Played > 0 && percents ? one.Played : 1)).CompareTo((double)two.Silver / (two.Played > 0 && percents ? two.Played : 1)); break; + case "Bronze": typeCompare = ((double)one.Bronze / (one.Played > 0 && percents ? one.Played : 1)).CompareTo((double)two.Bronze / (two.Played > 0 && percents ? two.Played : 1)); break; + case "Played": typeCompare = one.Played.CompareTo(two.Played); break; + case "Qualified": typeCompare = ((double)one.Qualified / (one.Played > 0 && percents ? one.Played : 1)).CompareTo((double)two.Qualified / (two.Played > 0 && percents ? two.Played : 1)); break; + case "Kudos": typeCompare = one.Kudos.CompareTo(two.Kudos); break; + case "AveKudos": typeCompare = one.AveKudos.CompareTo(two.AveKudos); break; + case "AveFinish": typeCompare = one.AveFinish.CompareTo(two.AveFinish); break; + case "Fastest": typeCompare = one.Fastest.CompareTo(two.Fastest); break; + case "Longest": typeCompare = one.Longest.CompareTo(two.Longest); break; + default: typeCompare = nameCompare; break; + } + } + + if (typeCompare == 0) { + typeCompare = nameCompare; + } + + return typeCompare; + }); + + gridDetails.DataSource = null; + gridDetails.DataSource = StatDetails; + gridDetails.Columns[columnName].HeaderCell.SortGlyphDirection = sortOrder; + } + private void gridDetails_SelectionChanged(object sender, EventArgs e) { + if (gridDetails.SelectedCells.Count > 0) { + gridDetails.ClearSelection(); + } + } + private void ToggleWinPercentageDisplay() { + CurrentSettings.ShowPercentages = !CurrentSettings.ShowPercentages; + SaveUserSettings(); + gridDetails.Invalidate(); + } + private void ShowShows() { + using (LevelDetails levelDetails = new LevelDetails()) { + levelDetails.LevelName = "Shows"; + List rounds = new List(); + for (int i = 0; i < StatDetails.Count; i++) { + rounds.AddRange(StatDetails[i].Stats); + } + rounds.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + + List shows = new List(); + int roundCount = 0; + int kudosTotal = 0; + bool won = false; + bool isFinal = false; + DateTime endDate = DateTime.MinValue; + for (int i = rounds.Count - 1; i >= 0; i--) { + RoundInfo info = rounds[i]; + if (roundCount == 0) { + endDate = info.End; + won = info.Qualified; + LevelStats levelStats = StatLookup[info.Name]; + isFinal = levelStats.IsFinal; + } + roundCount++; + kudosTotal += info.Kudos; + if (info.Round == 1) { + shows.Insert(0, new RoundInfo() { Name = isFinal ? "Final" : string.Empty, End = endDate, Start = info.Start, StartLocal = info.StartLocal, Kudos = kudosTotal, Qualified = won, Round = roundCount, ShowID = info.ShowID, Tier = won ? 1 : 0 }); + roundCount = 0; + kudosTotal = 0; + } + } + levelDetails.RoundDetails = shows; + levelDetails.StatsForm = this; + levelDetails.ShowDialog(this); + } + } + private void ShowRounds() { + using (LevelDetails levelDetails = new LevelDetails()) { + levelDetails.LevelName = "Rounds"; + List rounds = new List(); + for (int i = 0; i < StatDetails.Count; i++) { + rounds.AddRange(StatDetails[i].Stats); + } + rounds.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + levelDetails.RoundDetails = rounds; + levelDetails.StatsForm = this; + levelDetails.ShowDialog(this); + } + } + private void ShowFinals() { + using (LevelDetails levelDetails = new LevelDetails()) { + levelDetails.LevelName = "Finals"; + List rounds = new List(); + for (int i = 0; i < StatDetails.Count; i++) { + rounds.AddRange(StatDetails[i].Stats); + } + rounds.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + + int keepShow = -1; + for (int i = rounds.Count - 1; i >= 0; i--) { + RoundInfo info = rounds[i]; + if (info.ShowID != keepShow && (info.Crown || (StatLookup.TryGetValue(info.Name, out LevelStats levelStats) && levelStats.IsFinal))) { + keepShow = info.ShowID; + } else if (info.ShowID != keepShow) { + rounds.RemoveAt(i); + } + } + levelDetails.RoundDetails = rounds; + levelDetails.StatsForm = this; + levelDetails.ShowDialog(this); + } + } + private void ShowWinGraph() { + List rounds = new List(); + for (int i = 0; i < StatDetails.Count; i++) { + rounds.AddRange(StatDetails[i].Stats); + } + rounds.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + + using (StatsDisplay display = new StatsDisplay() { Text = "Wins Per Day" }) { + DataTable dt = new DataTable(); + dt.Columns.Add("Date", typeof(DateTime)); + dt.Columns.Add("Wins", typeof(int)); + dt.Columns.Add("Finals", typeof(int)); + dt.Columns.Add("Shows", typeof(int)); + + if (rounds.Count > 0) { + DateTime start = rounds[0].StartLocal; + int currentWins = 0; + int currentFinals = 0; + int currentShows = 0; + for (int i = 0; i < rounds.Count; i++) { + RoundInfo info = rounds[i]; + if (info.PrivateLobby) { continue; } + + LevelStats levelStats = null; + if (info.Round == 1) { + currentShows++; + } + if (info.Crown || (StatLookup.TryGetValue(info.Name, out levelStats) && levelStats.IsFinal)) { + currentFinals++; + if (info.Qualified) { + currentWins++; + } + } + + if (info.StartLocal.Date != start.Date) { + dt.Rows.Add(start.Date, currentWins, currentFinals, currentShows); + + int missingCount = (int)(info.StartLocal.Date - start.Date).TotalDays; + while (missingCount > 1) { + missingCount--; + start = start.Date.AddDays(1); + dt.Rows.Add(start, 0, 0, 0); + } + + currentWins = 0; + currentFinals = 0; + currentShows = 0; + start = info.StartLocal; + } + } + + dt.Rows.Add(start.Date, currentWins, currentFinals, currentShows); + } else { + dt.Rows.Add(DateTime.Now.Date, 0, 0, 0); + } + + display.Details = dt; + display.ShowDialog(this); + } + } + private void LaunchHelpInBrowser() { + try { + Process.Start(@"https://github.com/ShootMe/FallGuysStats"); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void UpdateGameExeLocation() { + if (string.IsNullOrEmpty(CurrentSettings.GameExeLocation)) { + string fallGuys = FindGameExeLocation(); + if (!string.IsNullOrEmpty(fallGuys)) { + CurrentSettings.GameExeLocation = fallGuys; + SaveUserSettings(); + } + } + } + private void LaunchGame(bool ignoreExisting) { + try { + UpdateGameExeLocation(); + + if (!string.IsNullOrEmpty(CurrentSettings.GameExeLocation) && File.Exists(CurrentSettings.GameExeLocation)) { + Process[] processes = Process.GetProcesses(); + string fallGuys = Path.GetFileNameWithoutExtension(CurrentSettings.GameExeLocation); + for (int i = 0; i < processes.Length; i++) { + string name = processes[i].ProcessName; + if (name.IndexOf(fallGuys, StringComparison.OrdinalIgnoreCase) >= 0) { + if (!ignoreExisting) { + MessageBox.Show(this, "Fall Guys is already running.", "Already Running", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + return; + } + } + + Process.Start(CurrentSettings.GameExeLocation); + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private string FindGameExeLocation() { + try { + // get steam install folder + object regValue = Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Valve\\Steam", "InstallPath", null); + string steamPath = (string)regValue; + + string fallGuys = Path.Combine(steamPath, "steamapps", "common", "Fall Guys", "FallGuys_client.exe"); + if (File.Exists(fallGuys)) { + return fallGuys; + } + // read libraryfolders.vdf from install folder to get games installation folder + // note: this parsing is terrible, but does technically work fine. There's a better way by specifying a schema and + // fully parsing the file or something like that. This is quick and dirty, for sure. + FileInfo libraryFoldersFile = new FileInfo(Path.Combine(steamPath, "steamapps", "libraryfolders.vdf")); + if (libraryFoldersFile.Exists) { + string[] libraryFoldersLines = File.ReadAllLines(libraryFoldersFile.FullName); + + // match strings against "drive letter-colon-double backslash-some path characters-final quote-end of line" + // see libraryfolders.vdf file in the Steam install folder's steamapps subfolder for example + foreach (string line in libraryFoldersLines) { + int indexStart = line.IndexOf("\t\t\""); + int indexRoot = line.IndexOf(":\\\\"); + if (indexStart < 0 || indexRoot < 0) { continue; } + + indexRoot = line.LastIndexOf('"'); + string libraryPath = line.Substring(indexStart + 3, indexRoot - indexStart - 3); + if (!string.IsNullOrEmpty(libraryPath)) { + // look for exe in standard location under library + fallGuys = Path.Combine(libraryPath, "steamapps", "common", "Fall Guys", "FallGuys_client.exe"); + if (File.Exists(fallGuys)) { + return fallGuys; + } + } + } + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + return string.Empty; + } + private void lblTotalFinals_Click(object sender, EventArgs e) { + try { + ShowFinals(); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void lblTotalShows_Click(object sender, EventArgs e) { + try { + ShowShows(); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void lblTotalRounds_Click(object sender, EventArgs e) { + try { + ShowRounds(); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void lblTotalWins_Click(object sender, EventArgs e) { + try { + ShowWinGraph(); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error Updating", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void menuStats_Click(object sender, EventArgs e) { + try { + ToolStripMenuItem button = sender as ToolStripMenuItem; + if (button == menuAllStats || button == menuSeasonStats || button == menuWeekStats || button == menuDayStats || button == menuSessionStats) { + if (!menuAllStats.Checked && !menuSeasonStats.Checked && !menuWeekStats.Checked && !menuDayStats.Checked && !menuSessionStats.Checked) { + button.Checked = true; + return; + } + + foreach (ToolStripItem item in menuStatsFilter.DropDownItems) { + if (item is ToolStripMenuItem menuItem && menuItem.Checked && menuItem != button) { + menuItem.Checked = false; + } + } + } + + if (button == menuAllPartyStats || button == menuSoloStats || button == menuPartyStats) { + if (!menuAllPartyStats.Checked && !menuSoloStats.Checked && !menuPartyStats.Checked) { + button.Checked = true; + return; + } + + foreach (ToolStripItem item in menuPartyFilter.DropDownItems) { + if (item is ToolStripMenuItem menuItem && menuItem.Checked && menuItem != button) { + menuItem.Checked = false; + } + } + + button = menuAllStats.Checked ? menuAllStats : menuSeasonStats.Checked ? menuSeasonStats : menuWeekStats.Checked ? menuWeekStats : menuDayStats.Checked ? menuDayStats : menuSessionStats; + } + + if (button == menuProfileMain || button == menuProfilePractice) { + if (!menuProfileMain.Checked && !menuProfilePractice.Checked) { + button.Checked = true; + return; + } + + foreach (ToolStripItem item in menuProfile.DropDownItems) { + if (item is ToolStripMenuItem menuItem && menuItem.Checked && menuItem != button) { + menuItem.Checked = false; + } + } + + button = menuAllStats.Checked ? menuAllStats : menuSeasonStats.Checked ? menuSeasonStats : menuWeekStats.Checked ? menuWeekStats : menuDayStats.Checked ? menuDayStats : menuSessionStats; + } + + for (int i = 0; i < StatDetails.Count; i++) { + LevelStats calculator = StatDetails[i]; + calculator.Clear(); + } + + ClearTotals(); + + int profile = menuProfileMain.Checked ? 0 : 1; + bool soloOnly = menuSoloStats.Checked; + List rounds = new List(); + + DateTime compareDate = menuAllStats.Checked ? DateTime.MinValue : menuSeasonStats.Checked ? SeasonStart : menuWeekStats.Checked ? WeekStart : menuDayStats.Checked ? DayStart : SessionStart; + for (int i = 0; i < AllStats.Count; i++) { + RoundInfo round = AllStats[i]; + if (round.Start > compareDate && round.Profile == profile && (menuAllPartyStats.Checked || round.InParty == !soloOnly)) { + rounds.Add(round); + } + } + + rounds.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + + if (rounds.Count > 0 && (button == menuWeekStats || button == menuDayStats || button == menuSessionStats)) { + int minShowID = rounds[0].ShowID; + + for (int i = 0; i < AllStats.Count; i++) { + RoundInfo round = AllStats[i]; + if (round.ShowID == minShowID && round.Start <= compareDate) { + rounds.Add(round); + } + } + } + + rounds.Sort(delegate (RoundInfo one, RoundInfo two) { + int showCompare = one.ShowID.CompareTo(two.ShowID); + return showCompare != 0 ? showCompare : one.Round.CompareTo(two.Round); + }); + + CurrentSettings.SelectedProfile = profile; + CurrentSettings.FilterType = menuAllStats.Checked ? 0 : menuSeasonStats.Checked ? 1 : menuWeekStats.Checked ? 2 : menuDayStats.Checked ? 3 : 4; + SaveUserSettings(); + + loadingExisting = true; + LogFile_OnParsedLogLines(rounds); + loadingExisting = false; + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void menuUpdate_Click(object sender, EventArgs e) { + try { + CheckForUpdate(false); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error Updating", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + public bool CheckForUpdate(bool silent) { +#if AllowUpdate + using (ZipWebClient web = new ZipWebClient()) { + string assemblyInfo = web.DownloadString(@"https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/Properties/AssemblyInfo.cs"); + + int index = assemblyInfo.IndexOf("AssemblyVersion("); + if (index > 0) { + int indexEnd = assemblyInfo.IndexOf("\")", index); + Version newVersion = new Version(assemblyInfo.Substring(index + 17, indexEnd - index - 17)); + if (newVersion > Assembly.GetEntryAssembly().GetName().Version) { + if (silent || MessageBox.Show(this, $"There is a new version of Fall Guy Stats available (v{newVersion.ToString(2)}). Do you wish to update now?", "Update Program", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK) { + byte[] data = web.DownloadData($"https://raw.githubusercontent.com/ShootMe/FallGuysStats/master/FallGuyStats.zip"); + string exeName = null; + using (MemoryStream ms = new MemoryStream(data)) { + using (ZipArchive zipFile = new ZipArchive(ms, ZipArchiveMode.Read)) { + foreach (var entry in zipFile.Entries) { + if (entry.Name.IndexOf(".exe", StringComparison.OrdinalIgnoreCase) > 0) { + exeName = entry.Name; + } + File.Move(entry.Name, $"{entry.Name}.old"); + entry.ExtractToFile(entry.Name, true); + } + } + } + + Process.Start(new ProcessStartInfo(exeName)); + Visible = false; + Close(); + return true; + } + } else if (!silent) { + MessageBox.Show(this, "You are at the latest version.", "Updater", MessageBoxButtons.OK, MessageBoxIcon.None); + } + } else if (!silent) { + MessageBox.Show(this, "Could not determine version.", "Error Updating", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } +#else + LaunchHelpInBrowser(); +#endif + return false; + } + private async void menuSettings_Click(object sender, EventArgs e) { + try { + using (Settings settings = new Settings()) { + settings.Icon = Icon; + settings.CurrentSettings = CurrentSettings; + string lastLogPath = CurrentSettings.LogPath; + + if (settings.ShowDialog(this) == DialogResult.OK) { + CurrentSettings = settings.CurrentSettings; + SaveUserSettings(); + + UpdateHoopsieLegends(); + + if (string.IsNullOrEmpty(lastLogPath) != string.IsNullOrEmpty(CurrentSettings.LogPath) || (!string.IsNullOrEmpty(lastLogPath) && lastLogPath.Equals(CurrentSettings.LogPath, StringComparison.OrdinalIgnoreCase))) { + await logFile.Stop(); + + string logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "Low", "Mediatonic", "FallGuys_client"); + if (!string.IsNullOrEmpty(CurrentSettings.LogPath)) { + logPath = CurrentSettings.LogPath; + } + logFile.Start(logPath, LOGNAME); + } + + overlay.ArrangeDisplay(CurrentSettings.FlippedDisplay, CurrentSettings.ShowOverlayTabs, CurrentSettings.HideWinsInfo, CurrentSettings.HideRoundInfo, CurrentSettings.HideTimeInfo, CurrentSettings.OverlayColor, + (int)(CurrentSettings.OverlayWidth * CurrentSettings.OverlayScale), (int)(CurrentSettings.OverlayHeight * CurrentSettings.OverlayScale), CurrentSettings.OverlayFontSerialized); + } + } + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void menuOverlay_Click(object sender, EventArgs e) { + ToggleOverlay(overlay); + } + private void ToggleOverlay(Overlay overlay) { + if (overlay.Visible) { + overlay.Hide(); + CurrentSettings.OverlayLocationX = overlay.Location.X; + CurrentSettings.OverlayLocationY = overlay.Location.Y; + CurrentSettings.OverlayWidth = overlay.Width; + CurrentSettings.OverlayHeight = overlay.Height; + CurrentSettings.OverlayVisible = false; + SaveUserSettings(); + } else { + overlay.TopMost = !CurrentSettings.OverlayNotOnTop; + overlay.Show(); + + CurrentSettings.OverlayVisible = true; + SaveUserSettings(); + + if (CurrentSettings.OverlayLocationX.HasValue && IsOnScreen(CurrentSettings.OverlayLocationX.Value, CurrentSettings.OverlayLocationY.Value, overlay.Width)) { + overlay.Location = new Point(CurrentSettings.OverlayLocationX.Value, CurrentSettings.OverlayLocationY.Value); + } else { + overlay.Location = Location; + } + } + } + private void menuHelp_Click(object sender, EventArgs e) { + LaunchHelpInBrowser(); + } + private void infoStrip_MouseEnter(object sender, EventArgs e) { + Cursor = Cursors.Hand; + } + private void infoStrip_MouseLeave(object sender, EventArgs e) { + Cursor = Cursors.Default; + } + private void menuLaunchFallGuys_Click(object sender, EventArgs e) { + try { + LaunchGame(false); + } catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + public bool IsOnScreen(int x, int y, int w) { + Screen[] screens = Screen.AllScreens; + foreach (Screen screen in screens) { + if (screen.WorkingArea.Contains(new Point(x, y)) || screen.WorkingArea.Contains(new Point(x + w, y))) { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Stats.resx b/Views/Stats.resx similarity index 98% rename from Stats.resx rename to Views/Stats.resx index ebf3e6074..a9f3039dc 100644 --- a/Stats.resx +++ b/Views/Stats.resx @@ -117,6 +117,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + + + 96, 17 + diff --git a/Views/StatsDisplay.Designer.cs b/Views/StatsDisplay.Designer.cs new file mode 100644 index 000000000..ccbfa331d --- /dev/null +++ b/Views/StatsDisplay.Designer.cs @@ -0,0 +1,115 @@ +namespace FallGuysStats { + partial class StatsDisplay { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(StatsDisplay)); + this.graph = new FallGuysStats.Graph(); + this.chkWins = new System.Windows.Forms.CheckBox(); + this.chkFinals = new System.Windows.Forms.CheckBox(); + this.chkShows = new System.Windows.Forms.CheckBox(); + ((System.ComponentModel.ISupportInitialize)(this.graph)).BeginInit(); + this.SuspendLayout(); + // + // graph + // + this.graph.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.graph.BackColor = System.Drawing.Color.Transparent; + this.graph.BackgroundColor = System.Drawing.Color.Transparent; + this.graph.ErrorImage = null; + this.graph.InitialImage = null; + this.graph.Location = new System.Drawing.Point(0, 36); + this.graph.Name = "graph"; + this.graph.Opacity = 0; + this.graph.Size = new System.Drawing.Size(614, 502); + this.graph.TabIndex = 0; + this.graph.TabStop = false; + // + // chkWins + // + this.chkWins.AutoSize = true; + this.chkWins.Location = new System.Drawing.Point(12, 13); + this.chkWins.Name = "chkWins"; + this.chkWins.Size = new System.Drawing.Size(50, 17); + this.chkWins.TabIndex = 1; + this.chkWins.Text = "Wins"; + this.chkWins.UseVisualStyleBackColor = true; + this.chkWins.CheckedChanged += new System.EventHandler(this.chkWins_CheckedChanged); + // + // chkFinals + // + this.chkFinals.AutoSize = true; + this.chkFinals.Location = new System.Drawing.Point(68, 13); + this.chkFinals.Name = "chkFinals"; + this.chkFinals.Size = new System.Drawing.Size(53, 17); + this.chkFinals.TabIndex = 2; + this.chkFinals.Text = "Finals"; + this.chkFinals.UseVisualStyleBackColor = true; + this.chkFinals.CheckedChanged += new System.EventHandler(this.chkFinals_CheckedChanged); + // + // chkShows + // + this.chkShows.AutoSize = true; + this.chkShows.Location = new System.Drawing.Point(127, 13); + this.chkShows.Name = "chkShows"; + this.chkShows.Size = new System.Drawing.Size(58, 17); + this.chkShows.TabIndex = 3; + this.chkShows.Text = "Shows"; + this.chkShows.UseVisualStyleBackColor = true; + this.chkShows.CheckedChanged += new System.EventHandler(this.chkShows_CheckedChanged); + // + // StatsDisplay + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(242)))), ((int)(((byte)(251))))); + this.ClientSize = new System.Drawing.Size(614, 538); + this.Controls.Add(this.chkShows); + this.Controls.Add(this.chkFinals); + this.Controls.Add(this.chkWins); + this.Controls.Add(this.graph); + this.ForeColor = System.Drawing.Color.Black; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.KeyPreview = true; + this.Name = "StatsDisplay"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Stats Display"; + this.Load += new System.EventHandler(this.StatsDisplay_Load); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.StatsDisplay_KeyDown); + ((System.ComponentModel.ISupportInitialize)(this.graph)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private Graph graph; + private System.Windows.Forms.CheckBox chkWins; + private System.Windows.Forms.CheckBox chkFinals; + private System.Windows.Forms.CheckBox chkShows; + } +} \ No newline at end of file diff --git a/Views/StatsDisplay.cs b/Views/StatsDisplay.cs new file mode 100644 index 000000000..390aefc8c --- /dev/null +++ b/Views/StatsDisplay.cs @@ -0,0 +1,37 @@ +using System; +using System.Data; +using System.Windows.Forms; +namespace FallGuysStats { + public partial class StatsDisplay : Form { + public DataTable Details { get; set; } + public StatsDisplay() { + InitializeComponent(); + } + + private void StatsDisplay_Load(object sender, EventArgs e) { + graph.DataSource = Details; + graph.YColumns[1] = true; + for (int i = 2; i < graph.YColumns.Length; i++) { + graph.YColumns[i] = false; + } + chkWins.Checked = true; + } + private void StatsDisplay_KeyDown(object sender, KeyEventArgs e) { + if (e.KeyCode == Keys.Escape) { + Close(); + } + } + private void chkWins_CheckedChanged(object sender, EventArgs e) { + graph.YColumns[1] = chkWins.Checked; + graph.Invalidate(); + } + private void chkFinals_CheckedChanged(object sender, EventArgs e) { + graph.YColumns[2] = chkFinals.Checked; + graph.Invalidate(); + } + private void chkShows_CheckedChanged(object sender, EventArgs e) { + graph.YColumns[3] = chkShows.Checked; + graph.Invalidate(); + } + } +} \ No newline at end of file diff --git a/Views/StatsDisplay.resx b/Views/StatsDisplay.resx new file mode 100644 index 000000000..1cb632457 --- /dev/null +++ b/Views/StatsDisplay.resx @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAE5TeXN0ZW0uRGF0YSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJl + PW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAABVTeXN0ZW0uRGF0YS5E + YXRhVGFibGUDAAAAGURhdGFUYWJsZS5SZW1vdGluZ1ZlcnNpb24JWG1sU2NoZW1hC1htbERpZmZHcmFt + AwEBDlN5c3RlbS5WZXJzaW9uAgAAAAkDAAAABgQAAADOBTw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rp + bmc9InV0Zi0xNiI/Pg0KPHhzOnNjaGVtYSB4bWxucz0iIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5v + cmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOm1zZGF0YT0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp4 + bWwtbXNkYXRhIj4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iVGFibGUxIj4NCiAgICA8eHM6Y29tcGxleFR5 + cGU+DQogICAgICA8eHM6c2VxdWVuY2U+DQogICAgICAgIDx4czplbGVtZW50IG5hbWU9IlgiIHR5cGU9 + InhzOmludCIgbXNkYXRhOnRhcmdldE5hbWVzcGFjZT0iIiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICAg + IDx4czplbGVtZW50IG5hbWU9IlkiIHR5cGU9InhzOmludCIgbXNkYXRhOnRhcmdldE5hbWVzcGFjZT0i + IiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICA8L3hzOnNlcXVlbmNlPg0KICAgIDwveHM6Y29tcGxleFR5 + cGU+DQogIDwveHM6ZWxlbWVudD4NCiAgPHhzOmVsZW1lbnQgbmFtZT0idG1wRGF0YVNldCIgbXNkYXRh + OklzRGF0YVNldD0idHJ1ZSIgbXNkYXRhOk1haW5EYXRhVGFibGU9IlRhYmxlMSIgbXNkYXRhOlVzZUN1 + cnJlbnRMb2NhbGU9InRydWUiPg0KICAgIDx4czpjb21wbGV4VHlwZT4NCiAgICAgIDx4czpjaG9pY2Ug + bWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIgLz4NCiAgICA8L3hzOmNvbXBsZXhUeXBl + Pg0KICA8L3hzOmVsZW1lbnQ+DQo8L3hzOnNjaGVtYT4GBQAAAMIIPGRpZmZncjpkaWZmZ3JhbSB4bWxu + czptc2RhdGE9InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206eG1sLW1zZGF0YSIgeG1sbnM6ZGlmZmdy + PSJ1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOnhtbC1kaWZmZ3JhbS12MSI+DQogIDx0bXBEYXRhU2V0 + Pg0KICAgIDxUYWJsZTEgZGlmZmdyOmlkPSJUYWJsZTExIiBtc2RhdGE6cm93T3JkZXI9IjAiIGRpZmZn + cjpoYXNDaGFuZ2VzPSJpbnNlcnRlZCI+DQogICAgICA8WD4xPC9YPg0KICAgICAgPFk+MTwvWT4NCiAg + ICA8L1RhYmxlMT4NCiAgICA8VGFibGUxIGRpZmZncjppZD0iVGFibGUxMiIgbXNkYXRhOnJvd09yZGVy + PSIxIiBkaWZmZ3I6aGFzQ2hhbmdlcz0iaW5zZXJ0ZWQiPg0KICAgICAgPFg+MjwvWD4NCiAgICAgIDxZ + Pjg8L1k+DQogICAgPC9UYWJsZTE+DQogICAgPFRhYmxlMSBkaWZmZ3I6aWQ9IlRhYmxlMTMiIG1zZGF0 + YTpyb3dPcmRlcj0iMiIgZGlmZmdyOmhhc0NoYW5nZXM9Imluc2VydGVkIj4NCiAgICAgIDxYPjM8L1g+ + DQogICAgICA8WT41PC9ZPg0KICAgIDwvVGFibGUxPg0KICAgIDxUYWJsZTEgZGlmZmdyOmlkPSJUYWJs + ZTE0IiBtc2RhdGE6cm93T3JkZXI9IjMiIGRpZmZncjpoYXNDaGFuZ2VzPSJpbnNlcnRlZCI+DQogICAg + ICA8WD40PC9YPg0KICAgICAgPFk+MzwvWT4NCiAgICA8L1RhYmxlMT4NCiAgICA8VGFibGUxIGRpZmZn + cjppZD0iVGFibGUxNSIgbXNkYXRhOnJvd09yZGVyPSI0IiBkaWZmZ3I6aGFzQ2hhbmdlcz0iaW5zZXJ0 + ZWQiPg0KICAgICAgPFg+NTwvWD4NCiAgICAgIDxZPjEwPC9ZPg0KICAgIDwvVGFibGUxPg0KICAgIDxU + YWJsZTEgZGlmZmdyOmlkPSJUYWJsZTE2IiBtc2RhdGE6cm93T3JkZXI9IjUiIGRpZmZncjpoYXNDaGFu + Z2VzPSJpbnNlcnRlZCI+DQogICAgICA8WD42PC9YPg0KICAgICAgPFk+ODwvWT4NCiAgICA8L1RhYmxl + MT4NCiAgICA8VGFibGUxIGRpZmZncjppZD0iVGFibGUxNyIgbXNkYXRhOnJvd09yZGVyPSI2IiBkaWZm + Z3I6aGFzQ2hhbmdlcz0iaW5zZXJ0ZWQiPg0KICAgICAgPFg+NzwvWD4NCiAgICAgIDxZPjM8L1k+DQog + ICAgPC9UYWJsZTE+DQogIDwvdG1wRGF0YVNldD4NCjwvZGlmZmdyOmRpZmZncmFtPgQDAAAADlN5c3Rl + bS5WZXJzaW9uBAAAAAZfTWFqb3IGX01pbm9yBl9CdWlsZAlfUmV2aXNpb24AAAAACAgICAIAAAAAAAAA + //////////8L + + + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAA + AACWpwD/jJ0A/4GSAP92hwD/d4gA/4CRAP+HmQD/jqAA/5OlAP+YqQD/mawA/52vAP+fsgD/orQA/6O2 + AP+ltwD/p7kA/6i7AP+qvAD/q70A/6y+AP+tvwD/rsAA/67AAP+vwQD/sMIA/7DCAP+wwgD/sMIA/7DC + AP+wwgD/sMIA/7HCAP+xwgD/sMIA/7DCAP+wwgD/sMIA/6/BAP+vwQD/rsAA/67AAP+tvgD/q70A/6q8 + AP+puwD/p7kA/6a4AP+ktgD/orQA/5+yAP+dsAD/mq0A/5ipAP+UpgD/kKIA/4qcAP+FlwD/fY4A/3iJ + AP90hQD/fY8A/4iZAP+TpAD/obIA/5iqAP+PoAD/h5gA/4WWAP+FlgD/iJoA/4+gAP+UpgD/l6oA/5qs + AP+drwD/n7IA/6K0AP+jtgD/pbcA/6a5AP+ougD/qbsA/6q9AP+svgD/rL4A/62/AP+tvwD/rsAA/6/B + AP+vwQD/r8EA/6/CAP+wwgD/sMIA/7DCAP+wwgD/sMIA/6/CAP+wwQD/r8EA/6/BAP+uwAD/rsAA/66/ + AP+tvwD/q70A/6q9AP+pvAD/qLoA/6e5AP+ltwD/o7UA/6GzAP+fsgD/na8A/5mtAP+YqgD/lacA/5Ci + AP+LnQD/hpcA/4CRAP+ElQD/hZYA/4ydAP+VpwD/nq8A/6u8AP+jtAD/m60A/5OlAP+QoQD/ipwA/4qc + AP+QoQD/lacA/5irAP+arQD/na4A/5+xAP+iswD/orUA/6W2AP+nuAD/p7kA/6i7AP+qvAD/q70A/6y+ + AP+tvwD/rsAA/6/BAP+wwgD/scMA/7LEAP+zxgD/tccA/7bJAP+3ygD/uMoA/7fJAP+1yAD/tMYA/7PE + AP+xwwD/sMIA/6/BAP+uwAD/rb8A/6y+AP+qvAD/qbsA/6e5AP+muAD/pLYA/6K0AP+hswD/n7IA/52u + AP+arQD/l6oA/5WnAP+RowD/jJ4A/4eYAP+DlQD/jp8A/5GiAP+YqgD/oLIA/6i5AP+1yAD/rb8A/6W3 + AP+esAD/mqwA/5CiAP+LnQD/kaIA/5aoAP+YqgD/m6wA/52vAP+fsQD/obMA/6K0AP+ktwD/p7kA/6e6 + AP+puwD/qr0A/6y+AP+tvwD/r8EA/7HDAP+yxAD/tcgA/7rNAP/B1AD/yNwB/8/iCf/U5hH/1ucY/9fo + Gv/V5xX/0eQO/8veBf/E1wD/vM8A/7fJAP+zxQD/scMA/6/BAP+tvwD/q70A/6m8AP+ougD/prgA/6S2 + AP+itAD/obIA/56xAP+drwD/ma0A/5irAP+VpwD/kqQA/42fAP+ImgD/iJoA/5epAP+crgD/o7QA/6q8 + AP+zxQD/wdQA/7nLAP+wwgD/qLoA/6O0AP+WqAD/jp8A/5KkAP+WqQD/masA/5usAP+dsAD/n7EA/6Cy + AP+jtQD/pLYA/6a4AP+ougD/qrwA/6u9AP+uwAD/sMIA/7PFAP+4ygD/wNMA/8zfAv/Z6hz/4u9D/+bw + av/m7on/5u2g/+bsrP/m7Kz/5uyh/+bui//n8G3/5PBJ/93tJf/R4wr/w9cA/7nMAP+0xgD/sMIA/62/ + AP+qvQD/qbsA/6e5AP+ltwD/o7UA/6GzAP+fsQD/na8A/5qtAP+YqwD/lqgA/5OkAP+OoAD/ipsA/42f + AP+gsQD/prgA/66/AP+2yAD/v9EA/8reAP/E1wD/u84A/7PFAP+svQD/na4A/5ChAP+TpQD/l6kA/5ms + AP+brQD/nbAA/5+xAP+gsgD/pLUA/6W3AP+nuQD/qbsA/6u9AP+uwAD/scMA/7XIAP++0QD/zN8B/93t + Mv/k7nn/5Oq1/+Hn2P/g5ef/4OXr/+Dl7P/g5ez/4OXs/+Dl7P/f5ev/4OXo/+Hm2P/j6bf/5e5+/+Hv + PP/S5Qr/wdUA/7fJAP+xwwD/rb8A/6q8AP+ouQD/prgA/6O1AP+itAD/nrEA/52uAP+brQD/masA/5ap + AP+TpQD/j6EA/4udAP+SpAD/qLkA/7HCAP+5ywD/wtQA/8jcAP/S5wD/zeEA/8faAP+/0gD/tsgA/6S2 + AP+SpAD/lKYA/5eqAP+arAD/m64A/52wAP+fsgD/obMA/6S2AP+mtwD/qLoA/6q8AP+tvwD/scMA/7fK + AP/D1wD/1+kh/+Lsg//h59L/3+Tq/97k6//f5Oj/4OXm/+Hl5//h5ef/4eXn/+Hl5//g5eb/4OXm/9/l + 5v/f5Oj/3uTr/97j6v/g5dL/4+yJ/93tMP/J3QD/us0A/7HEAP+tvwD/qbsA/6a4AP+ktgD/obMA/5+y + AP+drwD/m60A/5mrAP+WqQD/lKYA/5CiAP+MngD/l6kA/7LEAP+9zwD/xNcA/8vfAP/Q5QD/1+wA/9Tp + AP/P4wD/ydwA/8HTAP+qvQD/lKYA/5WnAP+YqgD/mqwA/5quAP+drwD/n7IA/6G0AP+jtQD/prgA/6m7 + AP+svgD/sMMA/7fKAP/I3AD/3OtM/9/nxf/e4uz/3ePo/97k5f/f5OX/4OXm/+Hm5//h5ef/4eXn/+Hm + 5//h5uf/4ebn/+Hl5//g5ub/4OTm/9/k5f/f4+X/3ePo/9zi6//f5cn/4e1f/9DjBv+6zQD/scMA/6y+ + AP+ougD/pLYA/6KzAP+fsgD/na8A/5utAP+YqwD/lqkA/5SmAP+RogD/jp8A/5yuAP+7zgD/x9oA/83h + AP/S5wD/1usA/9nuAP/X7QD/1eoA/9DlAP/I3AD/sMMA/5aoAP+WqAD/mKsA/5qtAP+brgD/na8A/5+y + AP+itAD/pLYA/6e5AP+rvQD/r8EA/7fKAP/J3QH/3uty/97j4v/c4er/3ePk/97k5P/f5eX/3+Tl/+Dl + 5v/h5uf/4eXn/+Hl5//h5uf/4ebn/+Hm5//h5ef/4Obn/+Dl5v/e5OX/3+Tk/97j4//c4uT/2+Hp/9zh + 5P/g6oz/0uUP/7vOAP+wwgD/qrwA/6a4AP+itAD/oLIA/5ywAP+brQD/l6sA/5aoAP+UpgD/kaMA/46g + AP+hswD/w9YA/87iAP/T6AD/1+wA/9nuAP/Z7wD/2O4A/9ftAP/U6gD/zuIA/7XIAP+XqQD/lqkA/5ir + AP+ZrAD/m64A/52uAP+fsgD/orQA/6S3AP+ougD/rb8A/7XHAP/I2wL/3Opy/93i5//b4eX/3OLj/97j + 5P/f5OX/3+Tl/9/l5f/g5eX/4eXn/+Hl5//h5ef/4ebn/+Hm5//h5ef/4ebn/+Dm5//g5eb/3uXl/9/k + 5f/e4+T/3eLj/9zi4//a4OT/2t/p/97ok//R4xD/uMsA/67AAP+ougD/o7UA/5+xAP+dsAD/m60A/5iq + AP+WpwD/lKYA/5GkAP+PoAD/pLYA/8jcAP/T6AD/1+wA/9nuAP/a7wD/2fAA/9jvAP/X7gD/1ewA/9Dm + AP+3ywD/l6gA/5aoAP+XqgD/mqsA/5uuAP+drgD/n7EA/6G0AP+ltwD/qrwA/7HEAP/C1QD/2ule/9zh + 4//b4OX/3OLi/9zi4//d4+T/3+Tl/9/k5v/f5eX/3+Xm/+Dl5//h5ef/4eXn/+Dl5v/g5eb/4eXn/+Hm + 5//g5eb/4OTm/9/l5f/f5OX/3uPk/93i5P/c4uP/2+Hi/9nf4//Z3uf/3eiE/8veB/+0xgD/qrwA/6S2 + AP+gsgD/nbAA/5qtAP+XqQD/lacA/5OlAP+QowD/jqAA/6a4AP/M4AD/1eoA/9jtAP/Z7wD/2e4A/9To + F//f9wH/1+8A/9TrAP/Q5gD/tsoA/5WnAP+VpwD/l6oA/5mrAP+brQD/na8A/5+wAP+iswD/prgA/6y/ + AP+5zAD/1OUx/9vi0v/Z3+b/2uHi/9zi4//d4eP/3ePk/+Dl5v/h5uf/4OXm/+Dl5v/g5ef/4Obm/+Dl + 5v/h5ef/4ebn/+Hm5v/g5ub/4OXm/+Dl5v/f5OX/3+Tl/+Dl5//e4+X/2+Hj/9vh4v/a4OH/2N3i/9ne + 3//b6VX/wdUA/67AAP+muAD/oLMA/52wAP+ZrQD/l6oA/5SnAP+SpAD/kKEA/46eAP+mtgD/zeEA/9fq + AP/Y7AD/2PAA/9brB/8yMyT/kZwr/9PoFP/a8gD/zuYA/7PIAP+RowD/k6UA/5apAP+YqwD/mq0A/52v + AP+fsQD/orQA/6i6AP+xwwD/yd0K/9vlpv/Y3ej/2eDh/9rg4v/b4eL/3OHj/+Hn6P/a3+H/2d7g/+Tp + 6v/g5ef/4OXm/+Dm5v/g5ub/4ebn/+Hm5//g5ef/4Obm/+Dm5v/f5OX/4ebn/+Ln6P/W29z/3eLk/97k + 5f/a4OH/2t/h/9je3//X3OX/2eG+/9PmIP+1yAD/qLoA/6GzAP+dsAD/mawA/5apAP+TpgD/kaMA/4+g + AP+NmgD/pLIA/83fAP/V6wD/1eoG/9PIMP/OgVz/AAAD/wAABf82OR3/lqMl/87kDv+xxgD/ipwA/5Ci + AP+UpwD/l6oA/5qsAP+crwD/n7EA/6K1AP+qvAD/ucsA/9TkT//Y3uD/2d7h/9nf4f/a4OL/2+Hj/+Dm + 5/+xt7f/WV9e/1hdXP+jqKj/4+jp/+Dm5v/h5ub/4ebn/+Hm6P/h5uj/4ebn/+Hm5//g5eb/4ebn/9vh + 4f+Fior/T1VU/2pwcP/Hzs7/3OPl/9nf4f/Y3uD/2N3f/9bb4//Y5nD/wtUA/6y9AP+itAD/nbAA/5mr + AP+WqAD/k6QA/5GiAP+QnQD/jJUA/6GrAP/K3QL/0Mgo/86JWf/LVmj/ykti/wEBAf8AAAD/AAAA/wAA + B/9CRxz/j54h/4iaB/+MngD/kqUA/5aoAP+ZqwD/nK4A/5+xAP+jtQD/rL4A/8LVA//X4aX/19zm/9je + 4P/Z3+H/2+Hj/97j5f/T2dr/UFdW/y41M/8xODf/Q0lI/8jNzv/k6er/4eXn/+Hm6P/h5uf/4ebo/+Hn + 6P/h5ef/4OXm/+br7P+ip6j/MTg3/zI4N/8rMjH/dXx7/97k5v/a4OH/2N7f/9fc3v/W2+L/1t66/8ve + Ev+xwwD/o7UA/5ywAP+YqwD/lacA/5KjAP+RoAD/kJoA/4uQAP+dnBX/x41K/8tVZv/KSmL/yk9g/8tR + YP8AAAD/AAAA/wAAAP8AAAD/AAAA/yQnHf+DlBv/iJsA/4+iAP+UpwD/mKoA/5utAP+fsQD/o7UA/6/C + AP/I2iT/2N3X/9fd4f/Y3d//2uDh/9vh4//g5eb/xMrL/z1ERP8wNjX/Mzk5/zg+Pv+5vr//5uvt/+Hm + 5//g5+j/4Obn/+Dm5//h5+j/4ebn/+Dm5//n7O3/h42N/y81NP8yODf/LTMz/1lgYP/a4OH/2uDi/9fd + 3//X3N7/1tze/9Tb3f/O3j7/t8kA/6S2AP+drwD/mKoA/5SmAP+RoQD/kZ4A/5CXAP+KjwP/qXBN/8xK + Zv/KTmD/ylBg/8tQYP/LUWH/AAAA/wAAAP8AAAD/AQEB/wAAAP8GBwr/cX8j/4aaAP+NnwD/kqUA/5ep + AP+arQD/nrAA/6S2AP+zxQD/zNlY/9jc6P/Y3d//2N7f/9rg4f/c4eP/4OXm/8XLzP8+RET/LzQ0/zI3 + N/86P0D/ur/A/+br7f/h5uf/4efn/+Hm5//h5uf/4Obn/+Hm5//g5uf/5+zt/4iNjv8vNTX/Mjc3/ywy + Mv9aYWH/2uDh/9rg4v/Y3d//19ze/9bc3f/V2ub/ztpw/7rMAP+mtwD/na8A/5epAP+TpQD/kaAA/5Cb + AP+PlAD/i4oN/7deWv/NUGP/y1Vk/8tUY//KUmH/ylBg/wAAAP8AAAD/AQEB/wYGBv8DAwT/BAQH/2Rw + Kf+EmAD/ip0A/5GjAP+WqAD/mawA/56wAP+ktgD/tMYA/87ZhP/Y3en/193e/9jd3//a4OL/3OHi/+Dm + 5//Gy8z/PkVF/zA1Nf84PT7/QEVG/7m+v//m6+3/4Obn/+Hm5//h5uf/4ubn/+Hm5//h5uf/4ebn/+fs + 7f+IjY7/Nzs8/zg9Pf8tMzP/WmFh/9rg4f/a4eL/2N7g/9fc3f/V293/1dvl/87Ymv+7zQD/p7gA/5yu + AP+WqAD/kqMA/5CfAP+QmQD/jpEA/42EGv+/WGD/zlVm/81baf/NW2r/y1Zl/8tTY/8AAAD/AAAA/wQE + BP8ICAj/AgIC/wICBf9TXSv/g5cE/4eaAP+PoQD/lKYA/5irAP+drwD/pLYA/7TGAP/Q2KX/2N3m/9fd + 3//Y3uD/2+Di/9zh4//g5uf/xsvM/z9FRf8zOTn/R0tM/0tPUf+4vb7/5+zt/+Hm5//h5uj/4ebn/+Hn + 5//i5uj/4ebn/+Hm5//n7O3/h4yN/0lMTv9HS0z/LzU1/1pgYf/a4OH/2+Hj/9je4P/W3N3/1tvd/9bb + 4v/P17j/ucoH/6e5AP+brQD/lacA/5GiAP+PnQD/j5cA/4yPAP+Sfij/xVVk/81YaP/LVGP/y1Nj/8xZ + aP/MWmj/AAAA/wAAAP8AAAD/AQEB/wgICP8CAQT/RUws/4KVDP+FmAD/jaAA/5OlAP+XqgD/nK4A/6S1 + AP+zxAj/0dm7/9jc5f/Y3d7/2d/g/9vh4v/c4uP/3+Xm/83T1P9GTEv/O0A//2dqav9YXFz/vMHC/+br + 7P/h5uf/4ebo/+Hn6P/h6Oj/4ebo/+Hm5//h5eb/5uzt/5CWl/9hZGT/XWBg/zI3N/9la2v/3uPl/9vh + 4//Y3uD/193f/9bc3f/V2uD/z9bG/7fHE/+muAD/mqwA/5SmAP+QoQD/jpsA/46VAP+LjQD/mXg2/8pR + ZP/LUWH/y1Rk/8tVZf/LVmX/y1Vk/wAAAP8AAAD/AAAA/wAAAP8HBwf/AAAA/zI3Jf+BlBn/hJcA/4ye + AP+SpAD/l6kA/5utAP+itAD/sMEN/9LZw//X3eT/2Nzf/9nf4P/b4OL/3OLj/93i5P/h5+f/k5iY/0xR + UP+NkI//lpub/9vg4f/i5+j/4ebn/+Hn6P/h5+f/4ejn/+Hn6P/h5+f/4ebm/+Po6f/Jzs//mJub/3h8 + ev9OVFP/sri4/+Dm5//a4OL/2N7g/9bd3v/W3N3/1dvf/83UzP+zwhz/pLYA/5mrAP+TpQD/kKAA/46a + AP+NkwD/iosA/6FyRf/MT2P/yk9f/8pQYP/KUWD/y1Ji/8tRYf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8gIxz/f5Ep/4KXAP+KnQD/kaQA/5aoAP+arAD/oLIA/62+Ef/S2cf/193j/9jd3//Z3+D/2uHi/9zi + 4//c4uP/3+Tl/+Dm5v/CyMj/vcPD/9ne3//i5+j/4Obn/+Hm6P/h5+f/4ufn/+Ln6P/i5+f/4ebn/+Hm + 5//g5eb/4ebn/9HW1/+7wMD/ys/Q/9/l5v/b4eP/2uDi/9je4P/X3d7/1tzd/9bb3//Q19H/sMAg/6Gz + AP+XqQD/kqQA/4+eAP+NmAD/jZEA/4qJBf+qbVP/zVFk/8pQYP/KUGD/y1Fh/8pQYP/KUGD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/FhcW/3qMOv+CmAD/iJ0A/4+jAP+VpwD/masA/56wAP+qug3/0djD/9jd + 5P/X3d//2d/h/9rh4v/c4uP/3eLj/97k5P/g5ub/4+np/+Ln6f/g5eb/4OXm/+Hn5//h5+j/4ujo/+Ln + 6P/i5+j/4ujo/+Ln6P/h5+f/4eXn/9/k5f/g5eb/5Onq/+Dl5//d4uP/3OHj/9vh4//Z3+D/19ze/9fb + 3f/V3N//0djS/628IP+fsQD/lqgA/5GjAP+OnQD/jZcA/42QAP+KiBD/s2dd/81SZP/KUGD/ylBg/8tR + Yf/LUWH/y1Fh/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wsLDf9zhEn/gZkH/4acAP+OogD/lKcA/5iq + AP+drwD/pbYI/87Wu//Z3uX/2N7f/9je4P/a3+H/2+Hi/9zh4//d4+T/4OXl/+Dl5v/f5OX/4OTm/+Dm + 5v/h5+j/4efn/+Ln6P/j5+j/4+jp/+Lo6f/h5+f/4ebn/+Dm5v/g5eb/3+Tl/9/k5f/e5OX/3ePk/9zi + 4//b4eP/2eDh/9jd3//W3N3/1tzg/8/Wy/+ouBf/nK4A/5SnAP+QogD/jp0A/4yWAP+MjwD/jIYd/7xl + Zv/MT2H/ylBg/8pRYf/LUWH/y1Fh/8tRYf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAH/ZHNM/4Gb + Gf+EmwD/jaEA/5OmAP+XqgD/nK4A/6CyAP/I0aP/2t7o/9je4P/Z3uD/2d/h/9vg4v/c4eP/3eLk/+Dk + 5v/g5eb/3+Tl/+Dl5v/h5uf/4ebn/+Ln6P/j5+j/4ujo/+Po6P/i6Oj/4ujo/+Hm5//h5uf/4OXm/9/k + 5f/e5OX/3+Tk/97j5f/b4uP/2uDi/9ng4f/Y3uD/193e/9fc4//L07v/orMI/5qsAP+TpgD/kKEA/42c + AP+NlgD/jI4A/5CELP/EYWv/zFNk/8lPX//LUWH/y1Fh/8tRYf/LUWH/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/1RjTf+DnTL/g5wB/4yhAP+TpgD/mKoA/5utAP+erwD/vsl+/9rf6v/Y3uD/2d/h/9rg + 4v/c4eL/3eLj/97j5f/f5Ob/3+Tl/+Dk5v/h5uf/4ebo/+Ln6P/i6Oj/4ujp/+Po6P/i6On/4ujp/+Lo + 6P/i5+j/4efo/+Hm5//g5eb/3+Tl/9/j5f/e4uX/3OLj/9vh4v/a4OL/2N7g/9jc3v/Y3eb/xM2e/52v + AP+ZqwD/k6UA/5GhAP+OnQD/jpcA/4uQAP+Wgjn/yVlp/8xTY//KUGD/y1Fh/8tRYf/LUWH/y1Fh/wAA + AP8CAgL/AgIC/wAAAP8AAAD/AAAA/wAAAP89Ulb/hqBJ/4KbCv+LoQD/k6cA/5mrAP+brQD/nK4A/7C+ + S//a3uj/2d7h/9rg4v/b4OL/3OHj/93i4//e4+T/3+Tm/9/k5v/h5eb/4ebn/+Hn6P/i6Oj/4ujo/+Lo + 6f/j6On/4+jq/+Po6f/i6Oj/4ejo/+Hn6P/h5uf/4OXm/9/k5f/e5OX/3eLk/93h4//b4eP/2d/i/9ne + 4P/X3d//2d7p/7fDbv+arQD/mKoA/5OlAP+RoQD/j50A/4+YAP+LkgD/oH5F/8tQY//KT1//y1Fh/8tR + Yf/LUWH/y1Fh/8tRYf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8BAQH/KkZd/4iiXf+CnBr/i6IA/5Oo + AP+ZqwD/nK4A/52vAP+jsxj/1dvW/9rf4//a4OL/2+Hj/9zi4//d4+T/3uPl/9/k5f/g5eb/4ebn/+Hm + 6P/h5+j/4ufo/+Lo6P/j6On/4+np/+Tp6f/k6en/4+jp/+Ln6f/i5+f/4efo/+Hm5//g5eb/3+Tl/97j + 5P/c4uP/2+Hi/9rg4v/Z3uH/2N3g/9fd4/+otzb/mq0A/5epAP+UpgD/kqIA/5CeAP+QmgD/jJcA/6t3 + Tv/MTmP/y1Bg/8tRYf/LUWH/y1Fh/8tRYf/LUWH/AAAA/wAAAP8AAAD/AAAA/wAAAP8BAQH/AwIC/xU4 + Xv+GoXP/hZ4x/4ykB/+UqQD/mqwA/52vAP+fsAD/nK4A/8XOn//b4en/2+Di/9zi4//d4uT/3uPl/9/k + 5f/g5eb/4ebn/+Hm6P/i5+f/4ufo/+Lo6P/j6On/4+np/+Pp6v/k6er/4+nq/+Tp6v/j6Oj/4ejo/+Ln + 5//h5uj/4ebn/9/k5v/e4+X/3ePk/9zh4//b4eL/2d/h/9nf5P/N1L//m60G/5uuAP+YqgD/lacA/5Ok + AP+ToQD/kp0A/5CbBP+4bVb/zE1i/8tRYf/LUWH/y1Fh/8tRYf/LUWH/y1Fh/wICAv8AAAD/AAAA/wAA + AP8AAAD/AAAA/wMCAP8ELV3/fJ2J/4qjRP+Mphr/lasA/5uuAP+esAD/oLIA/52vAP+tu0v/2uDn/9vh + 4//d4eP/3ePk/9/k5f/g5eb/4OXn/+Hm5//h5+f/4ejn/+Lo6P/i6On/4+jp/+Po6f/k6er/5Orr/+Tp + 6//k6On/4ujp/+Ln6f/i5+j/4efo/+Hm5//g5eb/3+Tl/97i5P/d4uP/2+Di/9nf4f/b4On/tcF0/5ir + AP+crgD/masA/5epAP+WpgD/lqMA/5WfAP+YoQv/xW1R/8tMYv/LUWH/y1Fh/8tRYf/KUGD/ylBg/8tR + Yf8HBwf/AAAA/wAAAP8AAAD/AAAA/wAAAP8HBQP/ACVa/2aMmP+UrFf/jqgv/5atB/+csAD/oLIA/6Kz + AP+hswD/nK0I/8vTtv/d4+n/3OLj/97j5P/f5OX/3+Xl/+Hm5//h5uj/4ebn/+Lo6P/i6Oj/4+fo/+Po + 6v/j6er/5Orr/+Tq6//l6uv/4+nq/+Pp6f/j6On/4ujo/+Ln6P/h5ub/4OXm/9/k5v/e4+X/3eHj/9vh + 4v/b4eX/0djQ/5yrG/+drwD/na8A/5qtAP+ZqwD/makA/5mmAP+YogD/pagT/9B0TP/LUGb/ylBg/8pQ + YP/KUGD/y1Fh/8tTY//KUGD/BgYG/wAAAP8AAAD/AAAA/wEBAf8AAAD/AAAA/wAfU/9CcZv/n7Rw/5Ks + Qf+Yrxv/nrIA/6K0AP+ktQD/pLUA/52vAP+quE3/3eLp/93i5P/d4+T/3+Tl/+Dk5v/h5uf/4efo/+Ln + 6P/i5+j/4ujp/+Lo6f/k6en/5Orq/+Tq6//k6uv/5err/+Tp6v/j6er/4+jp/+Ho6P/i5uj/4Obn/+Dl + 5//g5eb/3uTl/9zi4//b4eP/3uPt/7C8c/+WqAD/oLEA/56wAP+drwD/nK0A/5yrAP+dqQD/m6YB/7ew + Gv/Udkj/y1Np/8lPX//KUGD/ylBg/8tSYv/LUmL/ylBg/wEBAf8AAAD/AAAA/wEBAf8BAQH/AAAA/wAA + AP8AIlP/F1GW/6G2jf+aslT/m7I2/6G1Df+ltwD/p7kA/6i5AP+mtwD/l6kB/77Hm//g5u7/3uPk/9/k + 5v/g5eb/4eXm/+Hn6P/i6Oj/4+jo/+Pn6f/j6en/4+rq/+Tr6//k6uv/5erq/+Xq6//k6ev/5Ojq/+Po + 6P/i6Oj/4ufo/+Hn6P/h5uf/4OXm/97k5f/c4uP/3+Xr/8fPuv+Rogz/oLIA/6K0AP+hswD/obIA/6Cw + AP+hrwD/oq0A/6OsEv/Mthr/0nFB/8pOZf/KUGD/y1Fh/8tRYf/KUGD/ylBg/8tRYf8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/ACRU/wE9jv+Lp5//qb5q/5+2SP+juCf/qLoF/6u8AP+rvQD/q7wA/6S2 + AP+Wph//zdTK/+Ln7P/f5OX/4Obn/+Dm5//h5uj/4ujo/+Lo6P/j5+j/4+jq/+Tq6v/k6uv/5err/+Xq + 6v/l6ur/5err/+Pp6v/i6On/4+fp/+Lo6P/h5+j/4eXn/+Dl5v/e4+T/4OXp/9Ta2/+ToTf/m60A/6a3 + AP+ltwD/pbYA/6S1AP+lswD/prIA/6awAP+wtC//2LYN/9BtPv/JTGP/y1Fh/8pQYP/KUGD/ylBg/8pQ + YP/LUWH/AAAA/wAAAP8AAAD/AQEB/wAAAP8AAAD/AAAA/wAlVP8AN4n/VX6g/7zNiv+lvFn/prw8/6q+ + Gv+uwAD/r8EA/6/BAP+uvwD/oLEA/5SjOf/T2dn/4+fr/+Dl5v/h5+f/4efo/+Lo6P/i6Oj/4+jp/+Tp + 6f/k6ev/5err/+Xq6//l6uv/5err/+Xq6v/k6uv/4+jp/+Po6f/i6Of/4efo/+Dm5v/f5OX/4ebp/9nf + 4/+UoVX/k6UA/6m6AP+quwD/qboA/6m6AP+puQD/qrcA/6u2AP+ttg//xbxB/9y0AP/QcUP/yk1k/8pQ + YP/LU2P/y1Zl/8tVZP/LUmL/yk9g/wAAAP8AAAD/AQEB/wYGBv8EBAT/AwMD/wQDAv8CKFf/ADqK/xRM + kP+4y6n/s8dv/6zBTP+uwjL/scQP/7PFAP+0xQD/s8UA/7LDAP+drwD/j55A/8vSzv/m6/H/4ujq/+Hn + 5//h5+j/4ujp/+Pp6f/k6ur/5err/+Xq6//l6ur/5err/+Tr6//l6uv/5Onr/+Pp6v/j6On/4efo/+Hm + 5//i5+j/5urw/9HY1/+Nm1j/j6AA/6y+AP+vwAD/r8AA/66/AP+uvgD/r70A/7C8AP+xuwD/ucI//9e7 + L//bsgD/0G9C/8pNZP/KUGD/zFdm/81aaf/NW2r/y1Zl/8tUZP8AAAD/AAAA/wQEBP8ICAj/AwMD/wUF + Bf8EAgH/AypZ/wA9iv8ANIP/fZ6v/8zbkv+yx2D/ssdG/7XILP+3ygr/uMoA/7nLAP+4ywD/t8kA/6Kz + AP+HmCL/rria/93i5v/n7PH/5Ors/+Pp6f/i6On/4+rq/+Xq6//l6ur/5Orr/+Xr6//k6+z/5Ovr/+Xq + 6//j6er/4+jp/+Tp6//n7PD/3uTo/7G7pf9/jzH/lKYA/7LDAP+1xwD/tcYA/7TFAP+zxAD/s8MA/7TC + AP+2wQD/usMY/83LY//dtAn/2rIA/89tQf/KTGT/ylFh/8xYZ//LU2P/y1Nj/8xYZ//MWWj/AAAA/wAA + AP8AAAD/AQEB/wgICP8EBAT/CQcE/wEnVv8APov/ADeD/x9UlP/J2rX/wdR5/7jNWP+5zT3/u84f/7zP + Af++0AD/vtAA/77RAP+8zwD/r8EA/4+hBf+LmkT/sbyh/9Xc2v/l6u//6O3y/+ft8P/n7O7/5uzt/+bs + 7f/m7O3/5u3u/+ft7//o7vD/6e7y/+Xr7v/V3Nr/srym/4eWT/+DlQz/pbcA/7jKAP+7zQD/us0A/7rM + AP+5ygD/uMkA/7nIAP+7xwD/vccE/8jRXP/cwzz/268A/9uzAP/QbkL/yUxj/8pQYP/KUWH/y1Rj/8tV + ZP/LVmb/y1Rk/wAAAP8AAAD/AAAA/wAAAP8GBgb/AAAA/wIBAf8AJVT/AD6M/wA7hf8ANIP/cZWx/93r + oP++1G3/vdNR/7/TNv/B1BX/wtYB/8PXAP/E2AD/xdkA/8TYAP/A0gD/rL4A/4+hBP+Gly3/lqNm/624 + mf/Ezb//0dnT/9ng3//c4+P/3OPj/9ng3//R2dT/xM3A/624m/+UoWr/gZE0/4aXCv+itAD/us0A/8LV + AP/D1gD/wtUA/8DTAP+/0QD/v9AA/7/OAP/AzQD/ws0A/8fSO//b13b/3bQH/9uwAP/bswD/0G5C/8pN + ZP/LUGD/ylBg/8pQYP/KUGD/y1Jh/8tRYf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/ACVU/wA+ + jP8APIb/ADmE/wxCjP/A07//1uiO/8PZZf/D2Ev/xNkx/8baFP/I3AD/yt4A/8vgAP/N4QD/zuIA/83h + AP/J3AD/u84A/6a5AP+TpgL/iJoQ/4SVIf+FlTD/hpY4/4WVOf+ElDL/gpMk/4OWE/+NoAT/n7IA/7XI + AP/F2AD/zN8A/8zhAP/M4AD/yd4A/8fbAP/F2AD/xNcA/8TVAP/E0wD/xtIA/8nUJv/Z4IL/378s/9qt + AP/arwD/2rIA/89tQf/KTmX/y1Nj/8pQYP/KUGD/y1Fh/8pQYP/KUGD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AgEA/wAmVf8APYv/ADyG/wA8hv8AM4H/M2Oe/+Dtvv/W6IX/yd9l/8rfTP/L4DX/zeIa/8/j + A//R5gD/0ucA/9TqAP/X7AD/2e4A/9rvAP/b7gb/2ewc/9PmIv/N4CL/yNof/8TWHv/E1h//xtkh/8ze + JP/S5ST/2Osi/9ruFP/a7wD/2e4A/9bsAP/U6QD/0eYA/8/kAP/M4QD/yt4B/8ndAv/J2gD/ytkE/83Z + Jv/Z5Hv/489X/9qsAP/arwD/2q8A/9uzAP/QckX/y1Jo/8tTYv/KUGD/ylBg/8tRYf/LUWH/y1Fh/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wIBAP8ELlv/AD+M/wA7hf8APIb/ADuF/wAzgv9cg6z/6/a6/9nt + hf/R5mj/0eZV/9PnQv/U6Sr/1usP/9ftAf/a7wD/3PEA/97xAP/h8QD/5PIN/+fyKv/q9D7/7PVM/+71 + VP/v9lj/7/ZZ/+72Vv/t9VD/6/RF/+fzM//l8xv/4vIB/97xAP/c8AD/2e4A/9brAP/T6QD/0OYO/87k + If/N4ir/zeAs/9DgQP/a6H3/5dpx/9ywBf/argD/2q8A/9qwAP/btAD/0HFE/8tTaf/KUWH/ylBg/8tR + Yf/LUWH/y1Fh/8tRYf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/ACpX/wFEj/8AO4X/ADyG/wA8 + hv8AO4X/ADSD/3SVtv/z/b7/4fOM/9vud//a7mf/2+9W/9zwQv/d8Sr/3/Ib/+HyE//j8hD/5vIV/+jy + I//r8S//7fI8/+/zRf/x9Ez/8/RP//P0T//y9E3/8PRI/+7yP//r8TT/6fIn/+byF//j8gn/4PIE/97x + Bf/b8Az/1+0g/9XrOP/T6kn/0uhW/9PnY//e74//6OKD/9yzDf/argD/2q8A/9uxAP/bsgD/27QA/9F1 + SP/MVmz/y1Rk/8lPX//LUWH/y1Fh/8tRYf/LUWH/AAAA/wEBAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAl + VP8APoz/ADuF/wA8hv8APIb/ADyF/wA6hP8AOIT/hI7K//z9z//s/J3/4/WI/+L0eP/i9Gn/4/Ra/+Tz + Tv/l80T/6PM8/+nyOv/s8jr/7vI+//DzQ//y80j/8/NL//TzTv/0807/8/NM//LzSP/w80T/7vI+/+zy + OP/p8jP/5/Mu/+TzLP/h8i//3/I4/93yR//a8Fr/2e9q/9zwfP/s9qb/6+OM/920D//ZrQD/27AA/9mu + AP/arwD/3LMA/9y2AP/RdEb/ylFn/8tTY//KUGD/y1Fh/8tRYf/LUWH/y1Fh/wAAAP8CAgL/AgIC/wAA + AP8AAAD/AAAA/wAAAP8AJVT/AD2L/wA7hf8AO4X/ADuF/wA7hf8APYf/AEGE/zEgvv+xZ///8erh//f/ + t//s+J3/6faN/+n1gP/q9XP/6/Rp/+zzX//u81f/8PNU//H0Uv/z9FP/8/NU//T0VP/09FT/8/NV//T0 + Vf/09FT/8/NS//H0Uf/v80//7fNO/+vzTv/o81D/5vNW/+P0X//h9Gz/4fV8/+v3lv/v+b7/juTA/7i1 + G//drQD/2q8A/9uwAP/arwD/2q8A/9uwAP/bswD/0G9C/8lMY//KUGD/y1Fh/8tRYf/LUWH/y1Fh/8tR + Yf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8DAgH/AipY/wFEj/8BQYn/AUKK/wFBif8AO4X/ADuF/wA9 + gP85K73/fw///5I9///avvP//P/S//f+sv/y+KD/8PaS//D2h//x9X7/8vV1//T0b//09Gv/9PRo//T0 + Zv/09GX/9PRk//T0Zv/09Gf/9PRn//T0af/09Wn/8/Rq//H1a//u9G3/7fRw/+v1d//q9YL/7/eW//r7 + t//Y9M7/VNWx/wDAjP+wshf/4K8A/9uwAP/bsAD/27AA/9uwAP/arwD/2rIA/89tQf/KTWT/y1Fh/8tR + Yf/LUWH/y1Fh/8tRYf/LUWH/AAAA/wAAAP8AAAD/AAAA/wAAAP8BAQH/BQMC/wEoVv8AQI3/AD6H/wA9 + hv8CRIv/ADyG/wA6hP8APID/Oiy+/4QY//96E///fhv//6lm/v/m0u///f7W//3/vv/5+qv/9/ed//f2 + lP/29oz/9vWH//X1g//19X//9fV9//X1fP/19X3/9fWA//b2g//29oX/9vaH//b2iv/19oz/9PaQ//X3 + l//6+af///vA/+P30/9/4MH/F8id/wC/jP8AwIz/s7MZ/+CvAP/bsAD/27AA/9qvAP/arwD/2q8A/9uz + AP/QbkH/yk1k/8tRYf/LUWH/y1Fh/8tRYf/LUWH/y1Fh/wMDA/8AAAD/AAAA/wAAAP8AAAD/AAAA/wUD + Af8CLFn/AUOO/wA/h/8CRYz/AUSL/wA8hv8AO4X/ADyA/zkrvf+EGP//fhr//3wX//97Fv//hSj//6pq + ///Zvfj/9O/o////1f///8T///22//36rP/6+KT/+Pif//f3nP/395v/9/ec//f3n//4+KP/+fim//z4 + q///+bH///u5///8xv/4+9f/zPLY/3LewP8byJ3/AMKR/wDDkf8AwpD/AMGM/7KyGf/frgD/27AA/9uw + AP/arwD/27AA/9uxAP/ctQD/0HFC/8lMY//LUWH/y1Fh/8tRYf/KUGD/ylBg/8tRYf8HBwf/AAAA/wAA + AP8AAAD/AAAA/wAAAP8GBAP/AilY/wFFkP8CRoz/AD+H/wJFjP8APIX/ADqE/wA+gv86LL7/gxf//30Z + //99Gf//gyP//4Ii//99Gf//gxv//2xQ//9Xq///mcn9/8nj9v/p8+3/+Prl///+3v///9n////X///9 + 1v///Nj///zb//v84f/u+uj/1vbw/6nv+P9x5Pj/Ls+v/wLDk/8AwIz/AMOR/wDCj/8AxJP/AMKP/wDA + jf+zsxn/4K8A/9qvAP/arwD/2q8A/9uxAP/bsgD/3LUA/9F0Rv/KUmj/ylBg/8pQYP/KUGD/y1Jh/8tT + Y//KUGD/BQUF/wAAAP8AAAD/AAAA/wEBAf8AAAD/AAAA/wAlVP8AP4z/AD+I/wA7hf8AO4X/ADuG/wA7 + hf8AP4L/PDHA/4QY//99Gf//fRr//4Ii//+BIP//gB///4gi//9WOv//AHj//wB1//8EfP//G4j//zKU + //9Lov//W6j//2a5//9p5f//X+P//1Ph//862///Idb//wrT//8A0P//AM32/wDBk/8AwIv/AMGO/wDD + kP8AwY3/AMGN/wDCjf8AwY3/s7MZ/+CwAP/arwD/2q8A/9qvAP/bsQD/27EA/9u0AP/RdUj/y1Np/8lP + X//KUGD/ylBg/8tSYv/LUmL/ylBg/wAAAP8AAAD/AAAA/wEBAf8BAQH/AAAA/wAAAP8AJlX/AD2L/wA7 + hf8APIb/ADuF/wA8hv8APIb/ADyA/zosvf+EGP//fRn//30Z//99Gf//fhv//38c//+EGv//Vjj//wB9 + //8Aef//AHn//wB3//8Adv//AHX//wBx//8Ahf//AM///wDQ//8Az///AM7//wDP//8A0P//ANH//wDP + 9v8AwpX/AMCL/wDBjf8AwY3/AMKO/wDCjf8Awo3/AMKO/7KyGf/grgD/27AA/9qvAP/arwD/2q8A/9qw + AP/bswD/0HBE/8pOZf/KUGD/y1Fh/8tRYf/KUGD/ylBg/8tRYf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/ACZV/wA+jP8APIb/ADuF/wA7hf8APIb/ADyG/wA9gf85K77/gxf//34Z//9+Gv//fRn//30Z + //99Gf//gRX//1U3//8Aff//AHn//wB5//8Aef//AHn//wB6//8Ad///AIj//wDQ//8A0P//AND//wDR + //8A0f//AND//wDQ//8AzvX/AMKV/wDAi/8Awo7/AMKO/wDCjf8AwY3/AMKO/wDCjv+zsxn/364A/9uw + AP/bsAD/27AA/9uwAP/bsAD/2rIA/89tQf/JTGP/y1Fh/8pQYP/KUGD/ylBg/8pQYP/LUWH/AAAA/wAA + AP8AAAD/AgIC/wEBAf8AAAD/AAAA/wAlVP8APoz/ADuF/wA8hv8AO4X/ADuF/wA8hv8APID/OSu9/4MX + //99Gf//fRn//30Z//99Gf//fhv//4Qb//9WN///AHz//wB9//8Aff//AH7//wB6//8AeP//AHf//wCI + //8A0P//ANH//wDR//8A0P//AND//wDR//8A0f//AM/2/wDDlv8AwIv/AMKO/wDBjf8Awo7/AMGN/wDB + jf8Awo7/s7MZ/9+uAP/arwD/2q8A/9qvAP/arwD/2q8A/9uzAP/QcUT/yk1k/8pQYP/LVGP/zFdm/8xW + Zv/LUmL/yk9f/wAAAP8AAAD/AQEB/wcHB/8DAwP/AwMD/wQDAv8CKVf/AD6L/wA7hf8APIb/AUGJ/wA+ + h/8AO4X/AD2B/zsuvv+EGP//giP//34a//9+G///fhr//34b//+CF///Vjf//wB9//8Af///AID//wCA + //8Afv//AHz//wB2//8Aif//ANH//wDR//8A0f//AM///wDR//8A0v//ANL//wDQ9v8AxJn/AMKP/wDB + jf8AwY3/AMGN/wDDkP8AwpD/AMGN/7OzGf/grwD/2q8A/9uxAP/bsAD/2q8A/9qvAP/bswD/0G5C/8pN + ZP/KUGD/zFdn/8xZaP/MWmn/y1dm/8tVZf8AAAD/AAAA/wMDA/8HBwf/AwMD/wYGBv8FAgH/AylY/wA+ + iv8AO4X/ADuF/wA9hv8AP4j/ADuE/wA9gf8/N8L/iSL//4Ii//+CI///hCX//4Ae//98GP//gRX//1Y3 + //8Afv//AH///wB7//8Ae///AH///wB+//8Ad///AIz//wDR//8A0f//ANH//wDQ//8A0f//ANL//wDS + //8A0fb/AMSZ/wDDkP8AwIz/AMGN/wDBjf8Awo7/AMOQ/wDBjf+zsxn/4bIA/9uyAP/bsgD/27EA/9uy + AP/bsQD/2rIA/89tQf/KTWT/ylFh/8xYZ//LVGP/y1Nj/8xXZ//MWWj/AAAA/wAAAP8AAAD/AQEB/wgI + CP8EBAT/CQcE/wAmVf8APov/ADyG/wA8hv8AO4X/ADuF/wA6hP8APYH/PTPC/4ce//9+Gv//fx3//4Ad + //+BH///fBj//4IW//9VN///AHz//wB6//8Ae///AH3//wB+//8Ae///AHb//wCI//8A0P//AND//wDR + //8A0P//AM///wDQ//8A0///ANH2/wDGnf8Awo7/AMCM/wDCjv8Awo7/AMGN/wDBjf8AwI7/s7MZ/+Gx + AP/bsQD/2q8A/9qvAP/bsAD/27EA/9uzAP/QbkL/yUxj/8pQYP/KUWD/y1Ni/8tVZP/MV2b/y1Rj/wAA + AP8AAAD/AAAA/wAAAP8FBQX/AAAA/wEBAP8AJVT/AD6M/wA8hv8APIb/ADyG/wA7hf8AO4X/ADyA/z40 + wP+IIP//giL//4Ih//+CIv//gB7//30Z//+BFf//Vjj//wB8//8Aef//AHn//wB5//8Aev//AHn//wB3 + //8Aif//ANH//wDR//8A0P//ANH//wDQ//8A0P//ANL//wDP9f8Aw5f/AMGM/wDBjf8Awo7/AMKO/wDC + jv8AwY3/AMGN/7OyGf/gsQD/27IA/9uxAP/bsQD/27EA/9uxAP/bswD/z21B/8pNZP/KUGD/ylBg/8pQ + YP/KUGD/y1Fh/8tQYP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/ACVU/wA+jP8APIb/ADyG/wA7 + hf8AP4f/AD6H/wA8gP86LL3/hBj//34a//9+Gv//fRn//30Z//99Gf//gRX//1Y5//8Af///AHn//wB6 + //8Aef//AHn//wB7//8Aef//AI///wDT//8A0v//AND//wDQ//8A0f//ANH//wDQ//8Azvb/AMKV/wDA + i/8AwY3/AMKO/wDCjv8AwY3/AMKP/wDCkP+zshn/4K8A/9uwAP/bsAD/27AA/9qvAP/arwD/2rIA/85t + Qf/KTmX/y1Rj/8pQYP/KUGD/y1Fh/8pQYP/KUGD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AwIA/wAm + Vf8APYv/ADyG/wA8hv8AO4X/AD2H/wA8hv8APID/OSu9/4MX//9+Gf//fRn//30Z//9+G///giL//4Qb + //9VN///AH7//wB5//8Aev//AHn//wB5//8Af///AH7//wCM//8A0f//ANL//wDQ//8Az///ANH//wDR + //8A0f//AM/2/wDClv8Awo7/AMKP/wDBjf8AwY3/AMGN/wDCj/8Awo7/s7IZ/9+uAP/arwD/27AA/9uw + AP/arwD/2q8A/9uzAP/Qc0X/y1Jp/8pSYv/KUGD/ylBg/8tRYf/LUWH/y1Fh/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wIBAP8ELlv/AECM/wA7hf8APIb/ADyG/wA7hf8AO4X/AD2B/zosvv+EGP//fRn//38c + //+AH///gB///38e//+GH///Vjj//wB8//8Aev//AHr//wB5//8AeP//AH3//wB6//8Ai///ANH//wDR + //8A0P//AND//wDR//8A0f//ANH//wDP9v8Awpb/AMGN/wDDkf8AwY3/AMGN/wDCjv8AwY3/AMGN/7Oz + Gf/grwD/27AA/9qvAP/arwD/2rAA/9qwAP/btAD/0HFE/8tTaf/KUmH/ylBg/8tRYf/LUWH/y1Fh/8tR + Yf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AClX/wFEj/8AO4X/ADyG/wA8hv8APIb/ADyG/wA8 + gP86K73/hBj//30Z//+CIv//gyT//4Ag//+DJP//iSP//1c6//8Aff//AHr//wB6//8Aev//AHn//wB6 + //8AfP//AIv//wDQ//8A0P//ANH//wDR//8A0f//ANH//wDR//8Az/b/AMOW/wDAi/8AwY3/AMKO/wDC + jv8Awo7/AMKO/wDCjv+zsxn/4K4A/9uwAP/arwD/2a4A/9uxAP/bsgD/27UA/9F1SP/MV2z/y1Vk/8lP + X//LUWH/y1Fh/8tRYf/LUWH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAlVP8APYv/ADyG/wA8 + hv8APIb/ADyG/wA7hf8APID/OSy+/4QY//98GP//fx3//4Yq//+EJv//gSH//4QZ//9XOf//AH3//wB6 + //8Aev//AHr//wB6//8Aef//AHb//wCI//8A0P//ANH//wDQ//8A0f//AND//wDQ//8A0f//AM/2/wDD + lv8AwYz/AMKO/wDCjv8Awo7/AMKO/wDCjv8AwY3/srIY/+CvAP/arwD/27AA/9quAP/arwD/3LMA/9y2 + AP/RdEb/yk9m/8tSYv/KUGD/y1Fh/8tRYf/LUWH/y1Fh/wAAAP8DAwP/AQEB/wAAAP8AAAD/AAAA/wAA + AP8AJlX/AD2L/wA7hf8AO4X/ADuF/wA7hf8APYb/AEKE/zosvv+EF///fRn//30a//9/Hf//fhr//34b + //+BFf//VTf//wB9//8Aev//AHr//wB6//8Aev//AHr//wB2//8Aif//ANH//wDR//8A0P//ANH//wDR + //8A0P//ANH//wDP9v8Aw5b/AMGM/wDCjf8AwY3/AMGN/wDBjf8AwY7/AMGO/7O0Gf/grwD/2q8A/9uw + AP/arwD/2q8A/9uwAP/bswD/0G5C/8lMY//KUGD/y1Fh/8tRYf/LUWH/y1Fh/8tRYf8AAAD/AAAA/wEB + Af8AAAD/AAAA/wAAAP8AAAD/ACVU/wA+jP8APof/AT+I/wA+h/8AO4X/AD2G/wA+gf86K77/hBf//34a + //99Gf//fRn//30Z//99Gf//ghb//1Y4//8Aff//AHr//wB6//8Aev//AHr//wB6//8Ad///AIn//wDR + //8A0f//AND//wDQ//8A0f//AND//wDR//8Az/b/AMOW/wDBjP8Awo7/AMKP/wDDkP8Awo//AMGO/wDC + jv+zsxn/4K8A/9qvAP/bsAD/27AA/9uwAP/arwD/2rIA/89tQf/KTWT/y1Fh/8tRYf/LUWH/y1Fh/8tR + Yf/LUWH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + + + \ No newline at end of file diff --git a/info.png b/info.png deleted file mode 100644 index d6eb3c183..000000000 Binary files a/info.png and /dev/null differ