Merge branch 'release/1.4.3' of Hesham/OWtrack into master

Fixed:

    #39
    #33
    #11
    #37
This commit is contained in:
Hesham 2018-12-22 00:19:53 +03:00 committed by HeshamTB
commit 5caf393ea8
8 changed files with 192 additions and 334 deletions

View File

@ -31,21 +31,21 @@ namespace OWTrack
private const string IS_RUNNING = "Running"; private const string IS_RUNNING = "Running";
private const string NOT_RUNNING = " Not running"; private const string NOT_RUNNING = " Not running";
private bool SRonce = false; private bool SRonce = false;
int dummy = 0; private string Version = Program.Version.ToString();
public MainForm() public MainForm()
{ {
InitializeComponent(); InitializeComponent();
tr = new Tracker(); tr = new Tracker();
loadSave(); loadSave();
checkStatus(); checkStatus();
update(); update();
label4.Text = Program.Version.ToString(); label4.Text = Version;
Text = "OWTrack " + Program.Version.ToString(); Text = "OWTrack " + Version;
} }
private void checkStatus() private void checkStatus()
{ {
Time.Text = DateTime.Now.ToString("h:mm tt"); Time.Text = DateTime.Now.ToString("h:mm tt");
try try
{ {
@ -56,7 +56,7 @@ namespace OWTrack
} }
else else
{ {
if (tr.TrackOW) if (tr.settings.TrackOW)
{ {
status.Text = NOT_RUNNING; status.Text = NOT_RUNNING;
status.ForeColor = Color.Black; status.ForeColor = Color.Black;
@ -71,6 +71,7 @@ namespace OWTrack
} }
} }
//Move to saveManeger.cs ?
private void loadSave() private void loadSave()
{ {
try try
@ -84,64 +85,62 @@ namespace OWTrack
if (saveManeger.saveExist()) if (saveManeger.saveExist())
{ {
try try
{ {
using (StreamReader st = new StreamReader(Paths.GetSaves())) try
{ {
string line = st.ReadLine(); tr = saveManeger.GetSavedTracker();
if (line.Contains("Overwatch.exe"))
{
tr = saveManeger.GetSavedTracker();
if (tr.startSR > 0) SRonce = true;
}
else
{
if (!tr.LoacteOW())
{
tr.gamePath = getGamePath();
}
}
st.Close();
} }
} catch (Exception)
{
MessageBox.Show("Could not load Save.\n" +
"Starting new save.");
tr = new Tracker();
}
if (tr.startSR > 0) SRonce = true;
if (tr.settings.GamePath == "" || tr.settings.GamePath == null)
{
if (!tr.LoacteOW())
{
tr.settings.GamePath = askForGamePath();
}
}
}
catch (Exception e) catch (Exception e)
{ {
MessageBox.Show(e.Message); MessageBox.Show(e.Message);
} }
} }
else if (!tr.LoacteOW()) else if (!tr.LoacteOW())
{ {
tr.gamePath = getGamePath(); tr.settings.GamePath = askForGamePath();
} }
ExeTrackCheckBx.Checked = tr.TrackOW; ExeTrackCheckBx.Checked = tr.settings.TrackOW;
SRCheckBx.Checked = tr.TrackSR; SRCheckBx.Checked = tr.settings.TrackSR;
update(); tr.StartNewSeission();
} }
private string getGamePath() private string askForGamePath()
{ {
openFileDialog1.Title = "Select Overwatch.exe"; openFileDialog1.Title = "Select Overwatch.exe";
openFileDialog1.DefaultExt = "exe"; openFileDialog1.DefaultExt = "exe";
openFileDialog1.Filter = "exe Files (*.exe)|*.exe|All files (*.*)|*.*"; openFileDialog1.Filter = "exe Files (*.exe)|*.exe|All files (*.*)|*.*";
DialogResult result = openFileDialog1.ShowDialog(); DialogResult result = openFileDialog1.ShowDialog();
if (result == DialogResult.OK) if (result == DialogResult.OK)
{ {
MessageBox.Show("Saved Overwatch.exe location.\nPress Clear to rest"); MessageBox.Show("Saved Overwatch.exe location.\nPress Clear to rest");
return openFileDialog1.FileName; return openFileDialog1.FileName;
} }
else return null; else return null;
} }
private void SRSystem(bool state) private void SRSystem(bool state)
{ {
srBut.Enabled = state; srBut.Enabled = state;
srTextBox.Enabled = state; srTextBox.Enabled = state;
tr.TrackSR = state; tr.settings.TrackSR = state;
} }
private void OWTrackFunc(bool state) private void OWTrackFunc(bool state) => tr.settings.TrackOW = state;
{
tr.TrackOW = state;
}
private void update() private void update()
{ {
@ -155,7 +154,19 @@ namespace OWTrack
else srLabel.Text = tr.startSR.ToString() + " - " + tr.srDiff(); else srLabel.Text = tr.startSR.ToString() + " - " + tr.srDiff();
srTextBox.Text = null; srTextBox.Text = null;
saveManeger.SaveJSON(tr); saveManeger.SaveJSON(tr);
} }
private void AddMatch()
{
Match match = new Match
{
StartSR = tr.startSR,
newSR = tr.newSR,
ChangeInSR = tr.srDiff(),
dateTime = DateTime.Now.Date
};
tr.GetCurrentSession().AddMatch(match);
}
#region Events #region Events
private void timer1_Tick(object sender, EventArgs e) => checkStatus(); private void timer1_Tick(object sender, EventArgs e) => checkStatus();
@ -188,13 +199,13 @@ namespace OWTrack
tr.rediceLoss(); tr.rediceLoss();
update(); update();
} }
} }
private void clearBut_Click(object sender, EventArgs e) private void clearBut_Click(object sender, EventArgs e)
{ {
tr.reset(); tr.reset();
update(); update();
} }
private void srBut_Click(object sender, EventArgs e) private void srBut_Click(object sender, EventArgs e)
{ {
@ -222,6 +233,7 @@ namespace OWTrack
} }
else tr.newSR = sr; else tr.newSR = sr;
} }
AddMatch();
update(); update();
} }
@ -239,14 +251,14 @@ namespace OWTrack
private void ChngOWPathBtn_Click(object sender, EventArgs e) private void ChngOWPathBtn_Click(object sender, EventArgs e)
{ {
tr.gamePath = getGamePath(); tr.settings.GamePath = askForGamePath();
update(); update();
} }
private void MainForm_FormClosing(object sender, FormClosingEventArgs e) private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{ {
notifyIcon1.Icon = null; notifyIcon1.Icon = null;
notifyIcon1.Dispose(); notifyIcon1.Dispose();
} }
#endregion #endregion
} }

View File

@ -113,12 +113,6 @@
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="saveManeger.cs" /> <Compile Include="saveManeger.cs" />
<Compile Include="Splash.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Splash.Designer.cs">
<DependentUpon>Splash.cs</DependentUpon>
</Compile>
<Compile Include="Tracker.cs" /> <Compile Include="Tracker.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@ -134,9 +128,6 @@
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<EmbeddedResource Include="Splash.resx">
<DependentUpon>Splash.cs</DependentUpon>
</EmbeddedResource>
<None Include="app.manifest" /> <None Include="app.manifest" />
<None Include="OWTrack_TemporaryKey.pfx" /> <None Include="OWTrack_TemporaryKey.pfx" />
<None Include="packages.config" /> <None Include="packages.config" />

View File

@ -1,98 +0,0 @@
namespace OWTrack
{
partial class Splash
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.pageSetupDialog1 = new System.Windows.Forms.PageSetupDialog();
this.progressBar1 = new System.Windows.Forms.ProgressBar();
this.splashLabel = new System.Windows.Forms.Label();
this.versionLabel = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// progressBar1
//
this.progressBar1.Location = new System.Drawing.Point(95, 57);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(196, 12);
this.progressBar1.TabIndex = 0;
//
// splashLabel
//
this.splashLabel.AutoSize = true;
this.splashLabel.Location = new System.Drawing.Point(171, 31);
this.splashLabel.Name = "splashLabel";
this.splashLabel.Size = new System.Drawing.Size(0, 13);
this.splashLabel.TabIndex = 1;
this.splashLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// versionLabel
//
this.versionLabel.AutoSize = true;
this.versionLabel.Location = new System.Drawing.Point(13, 91);
this.versionLabel.Name = "versionLabel";
this.versionLabel.Size = new System.Drawing.Size(0, 13);
this.versionLabel.TabIndex = 2;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label1.ForeColor = System.Drawing.Color.Gray;
this.label1.Location = new System.Drawing.Point(13, 13);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(89, 24);
this.label1.TabIndex = 3;
this.label1.Text = "OWtrack";
//
// Splash
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(402, 116);
this.Controls.Add(this.label1);
this.Controls.Add(this.versionLabel);
this.Controls.Add(this.splashLabel);
this.Controls.Add(this.progressBar1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "Splash";
this.Text = "Splash";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.PageSetupDialog pageSetupDialog1;
private System.Windows.Forms.ProgressBar progressBar1;
private System.Windows.Forms.Label splashLabel;
private System.Windows.Forms.Label versionLabel;
private System.Windows.Forms.Label label1;
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace OWTrack
{
public partial class Splash : Form
{
public Splash()
{
InitializeComponent();
}
}
}

View File

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="pageSetupDialog1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@ -30,37 +30,63 @@ namespace OWTrack
class Tracker class Tracker
{ {
public int wins, losses, startSR, newSR, totalMatches = 0; public int wins, losses, startSR, newSR, totalMatches = 0;
public string gamePath;
public void Track() { }//Deserailize here
public void reset() { wins = 0; losses = 0; startSR = 0; newSR = 0; gamePath = null; }
public void addWin() => wins++; public void addWin() => wins++;
public void addLoss() => losses++; public void addLoss() => losses++;
public void reduceWin() => wins--; public void reduceWin() => wins--;
public void rediceLoss() => losses--; public void rediceLoss() => losses--;
public int GetWins() { return wins; } public int GetWins() { return wins; }
public int GetLosses() { return losses; } public int GetLosses() { return losses; }
public int GetTotalMatches() { return wins + losses; }
public void setNewSR(int SR) { newSR = SR; } public void setNewSR(int SR) { newSR = SR; }
public int srDiff() { return newSR - startSR; } public int srDiff() { return newSR - startSR; }
public bool TrackOW = true; public Settings settings = new Settings();
public bool TrackSR = true; public List<Session> sessions = new List<Session>();
public int GetTotalMatches()
struct ProgramFiles
{ {
public static readonly string C = "C:\\Program Files"; int number = 0;
public static readonly string D = "D:\\Program Files"; foreach (var session in sessions)
public static readonly string E = "E:\\Program Files"; {
public static readonly string F = "F:\\Program Files"; number += session.TotalMatches;
}
return number;
}
public int GetCurrentSessionMatches()
{
return sessions.Last().TotalMatches;
}
public void reset()
{
wins = 0;
losses = 0;
startSR = 0;
newSR = 0;
settings.Reset();
sessions.Clear();
StartNewSeission();
}
public void StartNewSeission()
{
Session ses = new Session(startSR);
sessions.Add(ses);
//Re do SR input!!
}
public Session GetCurrentSession()
{
return sessions.Last();
} }
public bool owRunning() public bool owRunning()
{ {
if (TrackOW) if (settings.TrackOW)
{ {
try try
{ {
bool isRunning = Process.GetProcessesByName("Overwatch") bool isRunning = Process.GetProcessesByName("Overwatch")
.FirstOrDefault(p => p.MainModule.FileName.StartsWith(gamePath)) != default(Process); .FirstOrDefault(p => p.MainModule.FileName.StartsWith(settings.GamePath)) != default(Process);
return isRunning; return isRunning;
} }
catch (Exception) catch (Exception)
@ -71,20 +97,20 @@ namespace OWTrack
} }
else return false; else return false;
} }
public bool LoacteOW() public bool LoacteOW()
{ {
try try
{ {
DriveInfo[] driveInfo = DriveInfo.GetDrives(); DriveInfo[] driveInfo = DriveInfo.GetDrives();
List<string> paths = new List<string>(); List<string> paths = new List<string>();
//Searches all drives (too long) //Searches all drives (too long)
foreach (var drive in driveInfo) //foreach (var drive in driveInfo)
{ //{
//paths.AddRange(GetFiles(drive.ToString(),"Overwatch.exe")); //paths.AddRange(GetFiles(drive.ToString(),"Overwatch.exe"));
} //}
paths.AddRange(GetFiles(ProgramFiles.C, "Overwatch.exe")); paths.AddRange(GetFiles(Paths.ProgramFiles.C, "Overwatch.exe"));
paths.AddRange(GetFiles(ProgramFiles.D, "Overwatch.exe")); paths.AddRange(GetFiles(Paths.ProgramFiles.D, "Overwatch.exe"));
if (paths.Count > 1) if (paths.Count > 1)
{ {
@ -93,19 +119,20 @@ namespace OWTrack
return true; return true;
} }
else if (paths.Count == 1) else if (paths.Count == 1
&& paths[0].Contains("Overwatch.exe"))
{ {
gamePath = paths[0]; settings.GamePath = paths[0];
return true; return true;
} }
else return false; else return false;
} }
catch (Exception e) catch (Exception e)
{ {
MessageBox.Show(e.Message); MessageBox.Show(e.Message);
return false; return false;
} }
} }
public static IEnumerable<string> GetFiles(string root, string searchPattern) public static IEnumerable<string> GetFiles(string root, string searchPattern)
{ {
@ -132,10 +159,64 @@ namespace OWTrack
} }
} }
class Settings
struct Settings
{ {
bool TrackSR, TrackOW; public bool TrackSR, TrackOW = true;
string OWpath; public string GamePath = "";
/// <summary>
/// Reset All values to defult
/// </summary>
public void Reset()
{
TrackOW = true;
TrackSR = true;
GamePath = "";
}
}
class Session
{
public int TotalMatches;
public int SkillChange;
public int StartSR;
public DateTime date;
public List<Match> Matches = new List<Match>();
/// <summary>
/// Start a new session with a starting Skill Rating
///</summary>
public Session(int StartSR)
{
this.StartSR = StartSR;
date = DateTime.Now.Date;
TotalMatches = 0;
}
public bool IsNewSession()
{
if (Matches.Count == 0) return true;
else return false;
}
public Match GetLastMatch()
{
return Matches.Last();
}
public void AddMatch(Match match)
{
this.Matches.Add(match);
this.TotalMatches = Matches.Count();
}
}
class Match
{
public Match() { }
public DateTime dateTime { get; set; }
public int StartSR;
public int newSR;
public int ChangeInSR;
} }
} }

View File

@ -24,7 +24,7 @@ using System.IO;
namespace OWTrack namespace OWTrack
{ {
public static class Paths public static class Paths
{ {
private static string curDir = Directory.GetCurrentDirectory(); private static string curDir = Directory.GetCurrentDirectory();
private static string SAVES = curDir + "/saves/data.json"; private static string SAVES = curDir + "/saves/data.json";
@ -32,10 +32,19 @@ namespace OWTrack
public static string GetJSON() { return JSON; } public static string GetJSON() { return JSON; }
public static string GetSaves() { return SAVES; } public static string GetSaves() { return SAVES; }
public static string GetCurrentDir() { return curDir; } public static string GetCurrentDir() { return curDir; }
public static class ProgramFiles
{
public static readonly string C = "C:\\Program Files";
public static readonly string D = "D:\\Program Files";
public static readonly string E = "E:\\Program Files";
public static readonly string F = "F:\\Program Files";
}
} }
class saveManeger class saveManeger
{ {
/// <summary> /// <summary>
/// Deserialize saved tracker instance. /// Deserialize saved tracker instance.
/// </summary> /// </summary>
@ -46,12 +55,18 @@ namespace OWTrack
{ {
return JsonConvert.DeserializeObject<Tracker>(File.ReadAllText(Paths.GetSaves())); return JsonConvert.DeserializeObject<Tracker>(File.ReadAllText(Paths.GetSaves()));
} }
catch (Exception e) catch (Exception)
{ {
throw e; Exception ex = new Exception("json");
throw ex;
} }
} }
/// <summary>
/// Deserialize saved tracker instance from a Custom path
/// </summary>
/// <param name="customPath"></param>
/// <returns></returns>
public static Tracker GetSavedTracker(string customPath) public static Tracker GetSavedTracker(string customPath)
{ {
try try
@ -72,7 +87,7 @@ namespace OWTrack
public static bool SaveJSON(Tracker tracker) public static bool SaveJSON(Tracker tracker)
{ {
try try
{ {
File.WriteAllText(Paths.GetSaves(), JsonConvert.SerializeObject(tracker, Formatting.Indented)); File.WriteAllText(Paths.GetSaves(), JsonConvert.SerializeObject(tracker, Formatting.Indented));
return true; return true;
} }

View File

@ -1,4 +1,4 @@
# OWtrack ![CI status](https://img.shields.io/badge/build-passing-brightgreen.svg) [![GitHub](https://img.shields.io/badge/Version-1.4.2-blue.svg)] (https://heshamgit.ddns.net/Hesham/OWtrack/releases) [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT) # OWtrack ![CI status](https://img.shields.io/badge/build-passing-brightgreen.svg) [![GitHub](https://img.shields.io/badge/Version-1.4.3-blue.svg)] (https://heshamgit.ddns.net/Hesham/OWtrack/releases) [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT)
Track your Overwatch preformance with a shi#ty UI Track your Overwatch preformance with a shi#ty UI