From 80484f6f15b621d7617e234f93b43ae32bcd8810 Mon Sep 17 00:00:00 2001 From: Anton Pupkov Date: Mon, 1 Feb 2016 23:06:00 -0800 Subject: [PATCH] Public Release Version 1.0.1 --- .gitignore | 2 + Dota2GSI/Dota2GSI.sln | 22 ++ Dota2GSI/Dota2GSI/Dota2GSI.csproj | 72 ++++++ Dota2GSI/Dota2GSI/GameState.cs | 150 ++++++++++++ Dota2GSI/Dota2GSI/GameStateListener.cs | 160 ++++++++++++ Dota2GSI/Dota2GSI/Nodes/Abilities.cs | 50 ++++ Dota2GSI/Dota2GSI/Nodes/Ability.cs | 24 ++ Dota2GSI/Dota2GSI/Nodes/Attributes.cs | 12 + Dota2GSI/Dota2GSI/Nodes/Auth.cs | 12 + Dota2GSI/Dota2GSI/Nodes/Hero.cs | 52 ++++ Dota2GSI/Dota2GSI/Nodes/Item.cs | 23 ++ Dota2GSI/Dota2GSI/Nodes/Items.cs | 126 ++++++++++ Dota2GSI/Dota2GSI/Nodes/Map.cs | 52 ++++ Dota2GSI/Dota2GSI/Nodes/Node.cs | 58 +++++ Dota2GSI/Dota2GSI/Nodes/Player.cs | 47 ++++ Dota2GSI/Dota2GSI/Nodes/Provider.cs | 18 ++ Dota2GSI/Dota2GSI/Properties/AssemblyInfo.cs | 36 +++ Dota2GSI/Dota2GSI/packages.config | 4 + LICENSE => LICENSE.md | 6 +- README.md | 242 ++++++++++++++++++- 20 files changed, 1162 insertions(+), 6 deletions(-) create mode 100644 Dota2GSI/Dota2GSI.sln create mode 100644 Dota2GSI/Dota2GSI/Dota2GSI.csproj create mode 100644 Dota2GSI/Dota2GSI/GameState.cs create mode 100644 Dota2GSI/Dota2GSI/GameStateListener.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Abilities.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Ability.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Attributes.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Auth.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Hero.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Item.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Items.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Map.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Node.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Player.cs create mode 100644 Dota2GSI/Dota2GSI/Nodes/Provider.cs create mode 100644 Dota2GSI/Dota2GSI/Properties/AssemblyInfo.cs create mode 100644 Dota2GSI/Dota2GSI/packages.config rename LICENSE => LICENSE.md (93%) diff --git a/.gitignore b/.gitignore index 94420dc..3d61437 100644 --- a/.gitignore +++ b/.gitignore @@ -234,3 +234,5 @@ _Pvt_Extensions # FAKE - F# Make .fake/ +*.nuspec +Dota2GSI/nuget.exe diff --git a/Dota2GSI/Dota2GSI.sln b/Dota2GSI/Dota2GSI.sln new file mode 100644 index 0000000..4de90ee --- /dev/null +++ b/Dota2GSI/Dota2GSI.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dota2GSI", "Dota2GSI\Dota2GSI.csproj", "{ED6F76C6-A3FC-40F2-9B9B-3C47093A4B6F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED6F76C6-A3FC-40F2-9B9B-3C47093A4B6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED6F76C6-A3FC-40F2-9B9B-3C47093A4B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED6F76C6-A3FC-40F2-9B9B-3C47093A4B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED6F76C6-A3FC-40F2-9B9B-3C47093A4B6F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Dota2GSI/Dota2GSI/Dota2GSI.csproj b/Dota2GSI/Dota2GSI/Dota2GSI.csproj new file mode 100644 index 0000000..cdf80a5 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Dota2GSI.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {ED6F76C6-A3FC-40F2-9B9B-3C47093A4B6F} + Library + Properties + Dota2GSI + Dota2GSI + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dota2GSI/Dota2GSI/GameState.cs b/Dota2GSI/Dota2GSI/GameState.cs new file mode 100644 index 0000000..6b6c883 --- /dev/null +++ b/Dota2GSI/Dota2GSI/GameState.cs @@ -0,0 +1,150 @@ +using System; +using Dota2GSI.Nodes; + +namespace Dota2GSI +{ + public class GameState + { + private Newtonsoft.Json.Linq.JObject _ParsedData; + private string json; + + private Auth auth; + private Provider provider; + private Map map; + private Player player; + private Hero hero; + private Abilities abilities; + private Items items; + private GameState previously; + private GameState added; + + /// + /// Initialises a new GameState object from JSON Data + /// + /// + public GameState(string json_data) + { + if (json_data.Equals("")) + { + json_data = "{}"; + } + + json = json_data; + _ParsedData = Newtonsoft.Json.Linq.JObject.Parse(json_data); + } + + public Auth Auth + { + get + { + if (auth == null) + auth = new Auth(GetNode("auth")); + + return auth; + } + } + + public Provider Provider + { + get + { + if (provider == null) + provider = new Provider(GetNode("provider")); + + return provider; + } + } + + public Map Map + { + get + { + if (map == null) + map = new Map(GetNode("map")); + + return map; + } + } + + public Player Player + { + get + { + if (player == null) + player = new Player(GetNode("player")); + + return player; + } + } + + public Hero Hero + { + get + { + if (hero == null) + hero = new Hero(GetNode("hero")); + + return hero; + } + } + + public Abilities Abilities + { + get + { + if (abilities == null) + abilities = new Abilities(GetNode("abilities")); + + return abilities; + } + } + + public Items Items + { + get + { + if (items == null) + items = new Items(GetNode("items")); + + return items; + } + } + + public GameState Previously + { + get + { + if (previously == null) + previously = new GameState(GetNode("previously")); + + return previously; + } + } + + public GameState Added + { + get + { + if (added == null) + added = new GameState(GetNode("added")); + + return added; + } + } + + private String GetNode(string name) + { + Newtonsoft.Json.Linq.JToken value; + + if (_ParsedData.TryGetValue(name, out value)) + return value.ToString(); + else + return ""; + } + + public override string ToString() + { + return json; + } + } +} diff --git a/Dota2GSI/Dota2GSI/GameStateListener.cs b/Dota2GSI/Dota2GSI/GameStateListener.cs new file mode 100644 index 0000000..cb3bf57 --- /dev/null +++ b/Dota2GSI/Dota2GSI/GameStateListener.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Dota2GSI +{ + public delegate void NewGameStateHandler(GameState gamestate); + + public class GameStateListener + { + private bool isRunning = false; + private int connection_port; + private HttpListener net_Listener; + private AutoResetEvent waitForConnection = new AutoResetEvent(false); + private GameState currentGameState; + + public GameState CurrentGameState + { + get + { + return currentGameState; + } + private set + { + currentGameState = value; + RaiseOnNewGameState(); + } + } + + /// + /// Gets the port that is being listened + /// + public int Port { get { return connection_port; } } + + /// + /// Returns whether or not the listener is running + /// + public bool Running { get { return isRunning; } } + + /// + /// Event for handing a newly received game state + /// + public event NewGameStateHandler NewGameState = delegate { }; + + /// + /// A GameStateListener that listens for connections on http://localhost:port/ + /// + /// + public GameStateListener(int Port) + { + connection_port = Port; + net_Listener = new HttpListener(); + net_Listener.Prefixes.Add("http://localhost:" + Port + "/"); + } + + /// + /// A GameStateListener that listens for connections to the specified URI + /// + /// The URI to listen to + public GameStateListener(string URI) + { + if (!URI.EndsWith("/")) + URI += "/"; + + Regex URIPattern = new Regex("^https?:\\/\\/.+:([0-9]*)\\/$", RegexOptions.IgnoreCase); + Match PortMatch = URIPattern.Match(URI); + + if (!PortMatch.Success) + throw new ArgumentException("Not a valid URI: " + URI); + + connection_port = Convert.ToInt32(PortMatch.Groups[1].Value); + + net_Listener = new HttpListener(); + net_Listener.Prefixes.Add(URI); + } + + /// + /// Starts listening for GameState requests + /// + public bool Start() + { + if (!isRunning) + { + Thread ListenerThread = new Thread(new ThreadStart(Run)); + try + { + net_Listener.Start(); + } + catch (HttpListenerException) + { + return false; + } + isRunning = true; + ListenerThread.Start(); + return true; + } + + return false; + } + + /// + /// Stops listening for GameState requests + /// + public void Stop() + { + isRunning = false; + } + + private void Run() + { + while (isRunning) + { + net_Listener.BeginGetContext(ReceiveGameState, net_Listener); + waitForConnection.WaitOne(); + waitForConnection.Reset(); + } + net_Listener.Stop(); + } + + private void ReceiveGameState(IAsyncResult result) + { + HttpListenerContext context = net_Listener.EndGetContext(result); + HttpListenerRequest request = context.Request; + string JSON; + + waitForConnection.Set(); + + using (Stream inputStream = request.InputStream) + { + using (StreamReader sr = new StreamReader(inputStream)) + JSON = sr.ReadToEnd(); + } + using (HttpListenerResponse response = context.Response) + { + response.StatusCode = (int)HttpStatusCode.OK; + response.StatusDescription = "OK"; + response.Close(); + } + CurrentGameState = new GameState(JSON); + } + + private void RaiseOnNewGameState() + { + foreach (Delegate d in NewGameState.GetInvocationList()) + { + if (d.Target is ISynchronizeInvoke) + (d.Target as ISynchronizeInvoke).BeginInvoke(d, new object[] { CurrentGameState }); + else + d.DynamicInvoke(CurrentGameState); + } + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Abilities.cs b/Dota2GSI/Dota2GSI/Nodes/Abilities.cs new file mode 100644 index 0000000..d8e0837 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Abilities.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Dota2GSI.Nodes +{ + public class Abilities : Node + { + private List abilities = new List(); + public readonly Attributes Attributes; + + private string json; + + public int Count { get { return abilities.Count; } } + + internal Abilities(string json_data) : base(json_data) + { + json = json_data; + + List abilities = _ParsedData.Properties().Select(p => p.Name).ToList(); + foreach (string ability_slot in abilities) + { + if (ability_slot.Equals("attributes")) + Attributes = new Attributes(_ParsedData[ability_slot].ToString()); + else + this.abilities.Add(new Ability(_ParsedData[ability_slot].ToString())); + } + } + + /// + /// Gets the ability in the selected index + /// + /// + /// + public Ability this[int index] + { + get + { + if (index > abilities.Count - 1) + return new Ability(""); + + return abilities[index]; + } + } + + public override string ToString() + { + return json; + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Ability.cs b/Dota2GSI/Dota2GSI/Nodes/Ability.cs new file mode 100644 index 0000000..ec0fa54 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Ability.cs @@ -0,0 +1,24 @@ +namespace Dota2GSI.Nodes +{ + public class Ability : Node + { + public readonly string Name; + public readonly int Level; + public readonly bool CanCast; + public readonly bool IsPassive; + public readonly bool IsActive; + public readonly int Cooldown; + public readonly bool IsUltimate; + + internal Ability(string json_data) : base(json_data) + { + Name = GetString("name"); + Level = GetInt("level"); + CanCast = GetBool("can_cast"); + IsPassive = GetBool("passive"); + IsActive = GetBool("ability_active"); + Cooldown = GetInt("cooldown"); + IsUltimate = GetBool("ultimate"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Attributes.cs b/Dota2GSI/Dota2GSI/Nodes/Attributes.cs new file mode 100644 index 0000000..8321150 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Attributes.cs @@ -0,0 +1,12 @@ +namespace Dota2GSI.Nodes +{ + public class Attributes : Node + { + public readonly int Level; + + internal Attributes(string json_data) : base(json_data) + { + Level = GetInt("level"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Auth.cs b/Dota2GSI/Dota2GSI/Nodes/Auth.cs new file mode 100644 index 0000000..01fc87a --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Auth.cs @@ -0,0 +1,12 @@ +namespace Dota2GSI.Nodes +{ + public class Auth : Node + { + public readonly string Token; + + internal Auth(string json_data) : base(json_data) + { + Token = GetString("token"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Hero.cs b/Dota2GSI/Dota2GSI/Nodes/Hero.cs new file mode 100644 index 0000000..64e3408 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Hero.cs @@ -0,0 +1,52 @@ +namespace Dota2GSI.Nodes +{ + public class Hero : Node + { + public readonly int ID; + public readonly string Name; + public readonly int Level; + public readonly bool IsAlive; + public readonly int SecondsToRespawn; + public readonly int BuybackCost; + public readonly int BuybackCooldown; + public readonly int Health; + public readonly int MaxHealth; + public readonly int HealthPercent; + public readonly int Mana; + public readonly int MaxMana; + public readonly int ManaPercent; + public readonly bool IsSilenced; + public readonly bool IsStunned; + public readonly bool IsDisarmed; + public readonly bool IsMagicImmune; + public readonly bool IsHexed; + public readonly bool IsMuted; + public readonly bool IsBreak; + public readonly bool HasDebuff; + + internal Hero(string json_data) : base(json_data) + { + ID = GetInt("id"); + Name = GetString("name"); + IsAlive = GetBool("alive"); + ID = GetInt("id"); + SecondsToRespawn = GetInt("respawn_seconds"); + BuybackCost = GetInt("buyback_cost"); + BuybackCooldown = GetInt("buyback_cooldown"); + Health = GetInt("health"); + MaxHealth = GetInt("max_health"); + HealthPercent = GetInt("health_percent"); + Mana = GetInt("mana"); + MaxMana = GetInt("max_mana"); + ManaPercent = GetInt("mana_percent"); + IsSilenced = GetBool("silenced"); + IsStunned = GetBool("stunned"); + IsDisarmed = GetBool("disarmed"); + IsMagicImmune = GetBool("magicimmune"); + IsHexed = GetBool("hexed"); + IsMuted = GetBool("muted"); + IsBreak = GetBool("break"); + HasDebuff = GetBool("has_debuff"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Item.cs b/Dota2GSI/Dota2GSI/Nodes/Item.cs new file mode 100644 index 0000000..59cc6ae --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Item.cs @@ -0,0 +1,23 @@ +namespace Dota2GSI.Nodes +{ + public class Item : Node + { + public readonly string Name; + public readonly string ContainsRune; + public readonly bool CanCast; + public readonly int Cooldown; + public readonly bool IsPassive; + public readonly int Charges; + + + internal Item(string json_data) : base(json_data) + { + Name = GetString("name"); + ContainsRune = GetString("contains_rune"); + CanCast = GetBool("can_cast"); + Cooldown = GetInt("cooldown"); + IsPassive = GetBool("passive"); + Charges = GetInt("charges"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Items.cs b/Dota2GSI/Dota2GSI/Nodes/Items.cs new file mode 100644 index 0000000..befa9f1 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Items.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Dota2GSI.Nodes +{ + public class Items : Node + { + private List inventory = new List(); + private List stash = new List(); + + public int CountInventory { get { return inventory.Count; } } + public int CountStash { get { return stash.Count; } } + + internal Items(string json_data) : base(json_data) + { + List slots = _ParsedData.Properties().Select(p => p.Name).ToList(); + foreach (string item_slot in slots) + { + if (item_slot.StartsWith("slot")) + this.inventory.Add(new Item(_ParsedData[item_slot].ToString())); + else + this.stash.Add(new Item(_ParsedData[item_slot].ToString())); + } + } + + /// + /// Gets the inventory item in the selected index + /// + /// + /// + public Item GetInventoryAt(int index) + { + if (index > inventory.Count - 1) + return new Item(""); + + return inventory[index]; + } + + /// + /// Gets the stash item in the selected index + /// + /// + /// + public Item GetStashAt(int index) + { + if (index > inventory.Count - 1) + return new Item(""); + + return inventory[index]; + } + + /// + /// Checks if item exists in the inventory + /// + /// + /// + public bool InventoryContains(string itemname) + { + foreach(Item inventory_item in this.inventory) + { + if (inventory_item.Name == itemname) + { + return true; + } + } + + return false; + } + + /// + /// Checks if item exists in the stash + /// + /// + /// + public bool StashContains(string itemname) + { + foreach (Item stash_item in this.stash) + { + if (stash_item.Name == itemname) + { + return true; + } + } + + return false; + } + + /// + /// Gets index of the item in the inventory + /// + /// + /// + public int InventoryIndexOf(string itemname) + { + int index = -1; + for (int x = 0; x < this.inventory.Count; x++) + { + if (this.inventory[x].Name == itemname) + { + return x; + } + } + + return index; + } + + /// + /// Gets index of the item in the stash + /// + /// + /// + public int StashIndexOf(string itemname) + { + int index = -1; + for (int x = 0; x < this.stash.Count; x++) + { + if (this.stash[x].Name == itemname) + { + return x; + } + } + + return index; + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Map.cs b/Dota2GSI/Dota2GSI/Nodes/Map.cs new file mode 100644 index 0000000..5b4653d --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Map.cs @@ -0,0 +1,52 @@ +namespace Dota2GSI.Nodes +{ + public enum DOTA_GameState + { + Undefined, + DOTA_GAMERULES_STATE_DISCONNECT, + DOTA_GAMERULES_STATE_GAME_IN_PROGRESS, + DOTA_GAMERULES_STATE_HERO_SELECTION, + DOTA_GAMERULES_STATE_INIT, + DOTA_GAMERULES_STATE_LAST, + DOTA_GAMERULES_STATE_POST_GAME, + DOTA_GAMERULES_STATE_PRE_GAME, + DOTA_GAMERULES_STATE_STRATEGY_TIME, + DOTA_GAMERULES_STATE_WAIT_FOR_PLAYERS_TO_LOAD + } + + public enum PlayerTeam + { + Undefined, + None, + Dire, + Radiant + } + + public class Map : Node + { + public readonly string Name; + public readonly int MatchID; + public readonly int GameTime; + public readonly int ClockTime; + public readonly bool IsDaytime; + public readonly bool IsNightstalker_Night; + public readonly DOTA_GameState GameState; + public readonly PlayerTeam Win_team; + public readonly string CustomGameName; + public readonly int Ward_Purchase_Cooldown; + + internal Map(string json_data) : base(json_data) + { + Name = GetString("name"); + MatchID = GetInt("matchid"); + GameTime = GetInt("game_time"); + ClockTime = GetInt("clock_time"); + IsDaytime = GetBool("daytime"); + IsNightstalker_Night = GetBool("nightstalker_night"); + GameState = GetEnum("game_state"); + Win_team = GetEnum("win_team"); + CustomGameName = GetString("customgamename"); + Ward_Purchase_Cooldown = GetInt("ward_purchase_cooldown"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Node.cs b/Dota2GSI/Dota2GSI/Nodes/Node.cs new file mode 100644 index 0000000..8bb5dcd --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Node.cs @@ -0,0 +1,58 @@ +using System; + +namespace Dota2GSI.Nodes +{ + public class Node + { + protected Newtonsoft.Json.Linq.JObject _ParsedData; + + internal Node(string json_data) + { + if (json_data.Equals("")) + { + json_data = "{}"; + } + _ParsedData = Newtonsoft.Json.Linq.JObject.Parse(json_data); + } + + internal string GetString(string Name) + { + Newtonsoft.Json.Linq.JToken value; + + if(_ParsedData.TryGetValue(Name, out value)) + return value.ToString(); + else + return ""; + } + + internal int GetInt(string Name) + { + Newtonsoft.Json.Linq.JToken value; + + if(_ParsedData.TryGetValue(Name, out value)) + return Convert.ToInt32(value.ToString()); + else + return -1; + } + + internal T GetEnum(string Name) + { + Newtonsoft.Json.Linq.JToken value; + + if(_ParsedData.TryGetValue(Name, out value) && !String.IsNullOrWhiteSpace(value.ToString())) + return (T)Enum.Parse(typeof(T), value.ToString(), true); + else + return (T)Enum.Parse(typeof(T), "Undefined", true); + } + + internal bool GetBool(string Name) + { + Newtonsoft.Json.Linq.JToken value; + + if(_ParsedData.TryGetValue(Name, out value) && value.ToObject()) + return value.ToObject(); + else + return false; + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Player.cs b/Dota2GSI/Dota2GSI/Nodes/Player.cs new file mode 100644 index 0000000..a227573 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Player.cs @@ -0,0 +1,47 @@ +namespace Dota2GSI.Nodes +{ + public enum PlayerActivity + { + Undefined, + Menu, + Playing + } + + public class Player : Node + { + public readonly string SteamID; + public readonly string Name; + public readonly PlayerActivity Activity; + public readonly int Kills; + public readonly int Deaths; + public readonly int Assists; + public readonly int LastHits; + public readonly int Denies; + public readonly int KillStreak; + public readonly PlayerTeam Team; + public readonly int Gold; + public readonly int GoldReliable; + public readonly int GoldUnreliable; + public readonly int GoldPerMinute; + public readonly int ExperiencePerMinute; + + internal Player(string json_data) : base(json_data) + { + SteamID = GetString("steamid"); + Name = GetString("name"); + Activity = GetEnum("activity"); + Kills = GetInt("kills"); + Deaths = GetInt("deaths"); + Assists = GetInt("assists"); + LastHits = GetInt("last_hits"); + Denies = GetInt("denies"); + KillStreak = GetInt("kill_streak"); + Team = GetEnum("team_name"); + Gold = GetInt("gold"); + GoldReliable = GetInt("gold_reliable"); + GoldUnreliable = GetInt("gold_unreliable"); + GoldPerMinute = GetInt("gpm"); + ExperiencePerMinute = GetInt("xpm"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Nodes/Provider.cs b/Dota2GSI/Dota2GSI/Nodes/Provider.cs new file mode 100644 index 0000000..0c28765 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Nodes/Provider.cs @@ -0,0 +1,18 @@ +namespace Dota2GSI.Nodes +{ + public class Provider : Node + { + public readonly string Name; + public readonly int AppID; + public readonly int Version; + public readonly string TimeStamp; + + internal Provider(string json_data) : base(json_data) + { + Name = GetString("name"); + AppID = GetInt("appid"); + Version = GetInt("version"); + TimeStamp = GetString("timestamp"); + } + } +} diff --git a/Dota2GSI/Dota2GSI/Properties/AssemblyInfo.cs b/Dota2GSI/Dota2GSI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..290bcd1 --- /dev/null +++ b/Dota2GSI/Dota2GSI/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Dota2GSI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Dota2GSI")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0147492d-b8eb-4d1b-8315-ec52a5866cc4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.1.0")] +[assembly: AssemblyFileVersion("1.0.1.0")] diff --git a/Dota2GSI/Dota2GSI/packages.config b/Dota2GSI/Dota2GSI/packages.config new file mode 100644 index 0000000..2abc396 --- /dev/null +++ b/Dota2GSI/Dota2GSI/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE.md similarity index 93% rename from LICENSE rename to LICENSE.md index 46540ca..b2b2c75 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,6 +1,4 @@ -The MIT License (MIT) - -Copyright (c) 2016 Anton Pupkov +JSON.Net is Copyright (c) 2007 James Newton-King Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 9475840..21dea42 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,240 @@ -# Dota2GSI -A C# library to intrerface the Game State Integration found in Dota 2. +# Dota 2 GSI (Game State Integration) +A C# library to interface the Game State Integration found in Dota 2. + +## What is Game State Integration + +Game State Integration hasn't been officially released for Dota 2, but it has been available for Counter-Strike: Global Offensive for a few months now. The concept is the same as CSGO's, you can read about [Counter-Strike Game State Integration here](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration). + +## About Dota 2 GSI + +This library provides easy means of implementing Game State Integration from Dota 2 into C# applications. Library listens for HTTP POST requests made by the game on a specific address and port. Upon receiving a request, the game state is parsed and can be used. + +JSON parsing is done though help of Newtonsoft's [JSON.Net Framework](http://www.newtonsoft.com/json). + +After starting the `GameStateListener` instance, it will continuously listen for incoming HTTP requests. Upon a received request, the contents will be parsed into a `GameState` object. + + +## Installation +Via NuGet: + +``` +Install-Package Dota2GSI +``` + +Manual installation: + +1. Get the [latest binaries](https://github.com/antonpup/Dota2GSI/releases/latest) +2. Get the [JSON Framework .dll by Newtonsoft](https://github.com/JamesNK/Newtonsoft.Json/releases) +3. Extract Newtonsoft.Json.dll from `Bin\Net45\Newtonsoft.Json.dll` +4. Add a reference to both Dota2GSI.dll and Newtonsoft.Json.dll in your project + +## Usage +1. Create a `GameStateListener` instance by providing a port or passing a specific URI: + +```C# +GameStateListener gsl = new GameStateListener(3000); //http://localhost:3000 +GameStateListener gsl = new GameStateListener("http://127.0.0.1:81/"); +``` + +**Please note**: If your application needs to listen to a URI other than `http://localhost:*/` (for example `http://192.168.2.2:100/`), you need to ensure that it is run with administrator privileges. +In this case, `http://127.0.0.1:*/` is **not** equivalent to `http://localhost:*/`. + +2. Create a handler: + +```C# +void OnNewGameState(GameState gs) +{ + //do stuff +} +``` + +3. Subscribe to the `NewGameState` event: + +```C# +gsl.NewGameState += new NewGameStateHandler(OnNewGameState); +``` + +4. Use `GameStateListener.Start()` to start listening for HTTP POST requests from the game client. This method will return `false` if starting the listener fails (most likely due to insufficient privileges). + +## Layout + +``` +GameState ++-- Auth + +-- Token ++-- Provider + +-- Name + +-- AppID + +-- Version + +-- TimeStamp ++-- Map + +-- Name + +-- MatchID + +-- GameTime + +-- ClockTime + +-- IsDaytime + +-- IsNightstalker_Night + +-- GameState + +-- Win_team + +-- CustomGameName + +-- Ward_Purchase_Cooldown ++-- Player + +-- SteamID + +-- Name + +-- Activity + +-- Kills + +-- Deaths + +-- Assists + +-- LastHits + +-- Denies + +-- KillStreak + +-- Team + +-- Gold + +-- GoldReliable + +-- GoldUnreliable + +-- GoldPerMinute + +-- ExperiencePerMinute ++-- Hero + +-- ID + +-- Name + +-- Level + +-- IsAlive + +-- SecondsToRespawn + +-- BuybackCost + +-- BuybackCooldown + +-- Health + +-- MaxHealth + +-- HealthPercent + +-- Mana + +-- MaxMana + +-- ManaPercent + +-- IsSilenced + +-- IsStunned + +-- IsDisarmed + +-- IsMagicImmune + +-- IsHexed + +-- IsMuted + +-- IsBreak + +-- HasDebuff ++-- Abilities + +-- Count + +-- Attributes + +-- Ability[] + +-- Name + +-- Level + +-- CanCast + +-- IsPassive + +-- IsActive + +-- Cooldown + +-- IsUltimate ++-- Items + +-- CountInventory + +-- GetInventoryAt( index ) + +-- InventoryContains( itemname ) + +-- InventoryIndexOf( itemname ) + +-- CountStash + +-- GetStashAt( index ) + +-- StashContains( itemname ) + +-- StashIndexOf( itemname ) ++-- Previously (Previous information from Game State) ++-- Added (Added information to the new Game State) +``` + +##### Examples: +```C# +int Health = gs.Hero.Health; // 560 +int MaxHealth = gs.Hero.MaxHealth; // 560 +string HeroName = gs.Hero.Name; //npc_dota_hero_omniknight +int Level = gs.Hero.Level; //1 + +Console.WriteLine("You are playing as " + HeroName + " with " + Health + "/" + MaxHealth + " health and level " + Level); +//You are playing as npc_dota_hero_omniknight with 560/560 health and level 1 + +``` + +## Null value handling + +In case the JSON did not contain the requested information, these values will be returned: + +Type|Default value +----|------------- +int|-1 +string| String.Empty + +All Enums have a value `enum.Undefined` that serves the same purpose. + +## Example program + +Prints "You bought an item" when you buy an item, and "It is night time" when it is night time. + +```C# +using Dota2GSI; +using System; + +namespace DOTA2GSI_sample +{ + static class Program + { + GameStateListener gsl; + + static void Main(string[] args) + { + gsl = new GameStateListener(4000); + gsl.NewGameState += new NewGameStateHandler(OnNewGameState); + + if (!gsl.Start()) + { + System.Windows.MessageBox.Show("GameStateListener could not start. Try running this program as Administrator.\r\nExiting."); + Environment.Exit(0); + } + Console.WriteLine("Listening for game integration calls..."); + } + + static void OnNewGameState(GameState gs) + { + if(gs.Map.GameState == DOTA_GameState.DOTA_GAMERULES_STATE_GAME_IN_PROGRESS) + { + if(gs.Added.Items.CountInventory > gs.Items.CountInventory) + { + Console.WriteLine("You bought an item"); + } + + if(!gs.Map.IsDaytime || gs.Map.IsNightstalker_Night) + { + Console.WriteLine("It is night time"); + } + } + } + } +} +``` + +You will also need to create a custom `gamestate_integration_*.cfg` in `game/dota/cfg/gamestate_integration/`, for example: +`gamestate_integration_test.cfg`: +``` +"Dota 2 Integration Configuration" +{ + "uri" "http://localhost:4000" + "timeout" "5.0" + "buffer" "0.1" + "throttle" "0.1" + "heartbeat" "30.0" + "data" + { + "provider" "1" + "map" "1" + "player" "1" + "hero" "1" + "abilities" "1" + "items" "1" + "allplayers" "1" + } +} + +``` + +**Please note**: In order to run this test application without explicit administrator privileges, you need to use the URI `http://localhost:` in this configuration file. + +## Credits +Special thanks to [rakijah](https://github.com/rakijah) for his CSGO Game State Integration library. +