diff --git a/Dota2GSI Example program/Dota2GSI Example program/Program.cs b/Dota2GSI Example program/Dota2GSI Example program/Program.cs index b2f249a..3b69055 100644 --- a/Dota2GSI Example program/Dota2GSI Example program/Program.cs +++ b/Dota2GSI Example program/Dota2GSI Example program/Program.cs @@ -1,8 +1,9 @@ -using System; +using Dota2GSI; +using Dota2GSI.EventMessages; +using Microsoft.Win32; +using System; using System.Diagnostics; using System.IO; -using Dota2GSI; -using Microsoft.Win32; using System.Threading; namespace Dota2GSI_Example_program @@ -24,7 +25,18 @@ static void Main(string[] args) } _gsl = new GameStateListener(4000); - _gsl.NewGameState += OnNewGameState; + + // There are many callbacks that can be subscribed. + // This example shows a few. + // _gsl.NewGameState += OnNewGameState; // `NewGameState` can be used alongside `GameEvent`. Just not in this example. + _gsl.GameEvent += OnGameEvent; // `GameEvent` can be used alongside `NewGameState`. + _gsl.TimeOfDayChanged += OnTimeOfDayChanged; + _gsl.TeamScoreChanged += OnTeamScoreChanged; + _gsl.PauseStateChanged += OnPauseStateChanged; + _gsl.PlayerGameplayEvent += OnPlayerGameplayEvent; + _gsl.TeamGameplayEvent += OnTeamGameplayEvent; + _gsl.InventoryItemAdded += OnInventoryItemAdded; + _gsl.InventoryItemRemoved += OnInventoryItemRemoved; if (!_gsl.Start()) { @@ -43,6 +55,131 @@ static void Main(string[] args) } } while (Console.ReadKey(true).Key != ConsoleKey.Escape); } + + private static void OnTimeOfDayChanged(TimeOfDayChanged game_event) + { + Console.WriteLine($"Is daytime: {game_event.IsDaytime} Is Nightstalker night: {game_event.IsNightstalkerNight}"); + } + private static void OnTeamScoreChanged(TeamScoreChanged game_event) + { + Console.WriteLine($"New score for {game_event.Team} is {game_event.New}"); + } + + private static void OnPauseStateChanged(PauseStateChanged game_event) + { + Console.WriteLine($"New pause state is {(game_event.New ? "paused" : "not paused")}"); + } + + private static void OnPlayerGameplayEvent(PlayerGameplayEvent game_event) + { + Console.WriteLine($"Player {game_event.Player.Details.Name} did a thing: " + game_event.Value.EventType); + } + + private static void OnTeamGameplayEvent(TeamGameplayEvent game_event) + { + Console.WriteLine($"Team {game_event.Team} did a thing: " + game_event.Value.EventType); + } + + private static void OnInventoryItemAdded(InventoryItemAdded game_event) + { + Console.WriteLine($"Player {game_event.Player.Details.Name} gained an item in their inventory: " + game_event.Value.Name); + } + + private static void OnInventoryItemRemoved(InventoryItemRemoved game_event) + { + Console.WriteLine($"Player {game_event.Player.Details.Name} lost an item from their inventory: " + game_event.Value.Name); + } + + private static void OnGameEvent(DotaGameEvent game_event) + { + if (game_event is ProviderUpdated provider) + { + Console.WriteLine($"Current Game version: {provider.New.Version}"); + Console.WriteLine($"Current Game time stamp: {provider.New.TimeStamp}"); + } + else if (game_event is PlayerDetailsChanged player_details) + { + Console.WriteLine($"Player Name: {player_details.New.Name}"); + Console.WriteLine($"Player Account ID: {player_details.New.AccountID}"); + } + else if (game_event is HeroDetailsChanged hero_details) + { + Console.WriteLine($"Player {hero_details.Player.Details.Name} Hero ID: " + hero_details.New.ID); + Console.WriteLine($"Player {hero_details.Player.Details.Name} Hero XP: " + hero_details.New.Experience); + Console.WriteLine($"Player {hero_details.Player.Details.Name} Hero has Aghanims Shard upgrade: " + hero_details.New.HasAghanimsShardUpgrade); + Console.WriteLine($"Player {hero_details.Player.Details.Name} Hero Health: " + hero_details.New.Health); + Console.WriteLine($"Player {hero_details.Player.Details.Name} Hero Mana: " + hero_details.New.Mana); + Console.WriteLine($"Player {hero_details.Player.Details.Name} Hero Location: " + hero_details.New.Location); + } + else if (game_event is AbilityUpdated ability) + { + Console.WriteLine($"Player {ability.Player.Details.Name} updated their ability: " + ability.New); + } + else if (game_event is TowerUpdated tower_updated) + { + if (tower_updated.New.Health < tower_updated.Previous.Health) + { + Console.WriteLine($"{tower_updated.Team} {tower_updated.Location} tower is under attack! Health: " + tower_updated.New.Health); + } + else if (tower_updated.New.Health > tower_updated.Previous.Health) + { + Console.WriteLine($"{tower_updated.Team} {tower_updated.Location} tower is being healed! Health: " + tower_updated.New.Health); + } + } + else if (game_event is TowerDestroyed tower_destroyed) + { + Console.WriteLine($"{tower_destroyed.Team} {tower_destroyed.Location} tower is destroyed!"); + } + else if (game_event is RacksUpdated racks_updated) + { + if (racks_updated.New.Health < racks_updated.Previous.Health) + { + Console.WriteLine($"{racks_updated.Team} {racks_updated.Location} {racks_updated.RacksType} racks are under attack! Health: " + racks_updated.New.Health); + } + else if (racks_updated.New.Health > racks_updated.Previous.Health) + { + Console.WriteLine($"{racks_updated.Team} {racks_updated.Location} {racks_updated.RacksType} tower are being healed! Health: " + racks_updated.New.Health); + } + } + else if (game_event is RacksDestroyed racks_destroyed) + { + Console.WriteLine($"{racks_destroyed.Team} {racks_destroyed.Location} {racks_destroyed.RacksType} racks is destroyed!"); + } + else if (game_event is AncientUpdated ancient_updated) + { + if (ancient_updated.New.Health < ancient_updated.Previous.Health) + { + Console.WriteLine($"{ancient_updated.Team} ancient is under attack! Health: " + ancient_updated.New.Health); + } + else if (ancient_updated.New.Health > ancient_updated.Previous.Health) + { + Console.WriteLine($"{ancient_updated.Team} ancient is being healed! Health: " + ancient_updated.New.Health); + } + } + else if (game_event is TeamNeutralItemsUpdated team_neutral_items_updated) + { + Console.WriteLine($"{team_neutral_items_updated.Team} neutral items updated: {team_neutral_items_updated.New}"); + } + else if (game_event is CourierUpdated courier_updated) + { + Console.WriteLine($"Player {courier_updated.Player.Details.Name} courier updated: {courier_updated.New}"); + } + else if (game_event is TeamDraftDetailsUpdated draft_details_updated) + { + Console.WriteLine($"{draft_details_updated.Team} draft details updated: {draft_details_updated.New}"); + } + else if (game_event is TeamDefeat team_defeat) + { + Console.WriteLine($"{team_defeat.Team} lost the game."); + } + else if (game_event is TeamVictory team_victory) + { + Console.WriteLine($"{team_victory.Team} won the game!"); + } + } + + // NewGameState example + static void OnNewGameState(GameState gs) { Console.Clear(); @@ -93,7 +230,7 @@ static void OnNewGameState(GameState gs) Console.WriteLine($"{level}"); } - foreach(var game_event in gs.Events) + foreach (var game_event in gs.Events) { if (game_event.EventType == Dota2GSI.Nodes.EventsProvider.EventType.Bounty_rune_pickup) { diff --git a/Dota2GSI Example program/Dota2GSI Example program/Properties/AssemblyInfo.cs b/Dota2GSI Example program/Dota2GSI Example program/Properties/AssemblyInfo.cs index cf3c2b5..35a60cb 100644 --- a/Dota2GSI Example program/Dota2GSI Example program/Properties/AssemblyInfo.cs +++ b/Dota2GSI Example program/Dota2GSI Example program/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Dota2GSI/CMakeLists.txt b/Dota2GSI/CMakeLists.txt index 23e9dc7..13d6c85 100644 --- a/Dota2GSI/CMakeLists.txt +++ b/Dota2GSI/CMakeLists.txt @@ -16,8 +16,33 @@ PROJECT(${PRODUCT} VERSION ${PRODUCT_VERSION} LANGUAGES CSharp) INCLUDE(CSharpUtilities) SET(SOURCES + Dota2EventsInterface.cs + EventDispatcher.cs + EventHandler.cs + EventsInterface.cs GameState.cs GameStateListener.cs + GameStateHandler.cs + EventMessages/AbilitiesEvents.cs + EventMessages/AuthEvents.cs + EventMessages/BaseEvent.cs + EventMessages/BuildingsEvents.cs + EventMessages/CouriersEvents.cs + EventMessages/DotaGameEvent.cs + EventMessages/DraftEvents.cs + EventMessages/GameplayEvents.cs + EventMessages/FullDetailsEvents.cs + EventMessages/HeroEvents.cs + EventMessages/ItemsEvents.cs + EventMessages/LeagueEvents.cs + EventMessages/MapEvents.cs + EventMessages/MinimapEvents.cs + EventMessages/NeutralItemsEvents.cs + EventMessages/PlayerEvents.cs + EventMessages/ProviderEvents.cs + EventMessages/RoshanEvents.cs + EventMessages/UpdateEvent.cs + EventMessages/WearablesEvents.cs Nodes/Roshan.cs Nodes/HeroProvider/HeroDetails.cs Nodes/CouriersProvider/Courier.cs @@ -60,6 +85,23 @@ SET(SOURCES Nodes/Provider.cs Nodes/Map.cs Nodes/Auth.cs + StateHandlers/AbilitiesHandler.cs + StateHandlers/AuthHandler.cs + StateHandlers/BuildingsHandler.cs + StateHandlers/CouriersHandler.cs + StateHandlers/DraftHandler.cs + StateHandlers/GameplayEventsHandler.cs + StateHandlers/FullDetailsHandler.cs + StateHandlers/HeroHandler.cs + StateHandlers/ItemsHandler.cs + StateHandlers/LeagueHandler.cs + StateHandlers/MapHandler.cs + StateHandlers/MinimapHandler.cs + StateHandlers/NeutralItemsHandler.cs + StateHandlers/PlayerHandler.cs + StateHandlers/ProviderHandler.cs + StateHandlers/RoshanHandler.cs + StateHandlers/WearablesHandler.cs ) SET(README "${CMAKE_SOURCE_DIR}/README.md") diff --git a/Dota2GSI/Dota2EventsInterface.cs b/Dota2GSI/Dota2EventsInterface.cs new file mode 100644 index 0000000..5b042d6 --- /dev/null +++ b/Dota2GSI/Dota2EventsInterface.cs @@ -0,0 +1,951 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class Dota2EventsInterface : EventsInterface + { + #region AbilitiesEvents + + public delegate void AbilitiesStateUpdatedHandler(AbilitiesUpdated game_event); + + /// + public event AbilitiesStateUpdatedHandler AbilitiesUpdated = delegate { }; + + public delegate void AbilityDetailsChangedHandler(AbilityDetailsChanged game_event); + + /// + public event AbilityDetailsChangedHandler AbilityDetailsChanged = delegate { }; + + public delegate void AbilityAddedHandler(AbilityAdded game_event); + + /// + public event AbilityAddedHandler AbilityAdded = delegate { }; + + public delegate void AbilityRemovedHandler(AbilityRemoved game_event); + + /// + public event AbilityRemovedHandler AbilityRemoved = delegate { }; + + public delegate void AbilityUpdatedHandler(AbilityUpdated game_event); + + /// + public event AbilityUpdatedHandler AbilityUpdated = delegate { }; + + #endregion + + #region AuthEvents + + public delegate void AuthUpdatedHandler(AuthUpdated game_event); + + /// + public event AuthUpdatedHandler AuthUpdated = delegate { }; + + #endregion + + #region BuildingsEvents + + public delegate void BuildingsUpdatedHandler(BuildingsUpdated game_event); + + /// + public event BuildingsUpdatedHandler BuildingsUpdated = delegate { }; + + public delegate void BuildingsLayoutUpdatedHandler(BuildingsLayoutUpdated game_event); + + /// + public event BuildingsLayoutUpdatedHandler BuildingsLayoutUpdated = delegate { }; + + public delegate void BuildingUpdatedHandler(BuildingUpdated game_event); + + /// + public event BuildingUpdatedHandler BuildingUpdated = delegate { }; + + public delegate void TeamBuildingUpdatedHandler(TeamBuildingUpdated game_event); + + /// + public event TeamBuildingUpdatedHandler TeamBuildingUpdated = delegate { }; + + public delegate void TeamBuildingDestroyedHandler(TeamBuildingDestroyed game_event); + + /// + public event TeamBuildingDestroyedHandler TeamBuildingDestroyed = delegate { }; + + public delegate void TowerUpdatedHandler(TowerUpdated game_event); + + /// + public event TowerUpdatedHandler TowerUpdated = delegate { }; + + public delegate void TowerDestroyedHandler(TowerDestroyed game_event); + + /// + public event TowerDestroyedHandler TowerDestroyed = delegate { }; + + public delegate void RacksUpdatedHandler(RacksUpdated game_event); + + /// + public event RacksUpdatedHandler RacksUpdated = delegate { }; + + public delegate void RacksDestroyedHandler(RacksDestroyed game_event); + + /// + public event RacksDestroyedHandler RacksDestroyed = delegate { }; + + public delegate void AncientUpdatedHandler(AncientUpdated game_event); + + /// + public event AncientUpdatedHandler AncientUpdated = delegate { }; + + public delegate void AncientDestroyedHandler(AncientDestroyed game_event); + + /// + public event AncientDestroyedHandler AncientDestroyed = delegate { }; + + #endregion + + #region CouriersEvents + + public delegate void CouriersUpdatedHandler(CouriersUpdated game_event); + + /// + public event CouriersUpdatedHandler CouriersUpdated = delegate { }; + + public delegate void CourierUpdatedHandler(CourierUpdated game_event); + + /// + public event CourierUpdatedHandler CourierUpdated = delegate { }; + + public delegate void TeamCourierUpdatedHandler(TeamCourierUpdated game_event); + + /// + public event TeamCourierUpdatedHandler TeamCourierUpdated = delegate { }; + + public delegate void CourierItemAddedHandler(CourierItemAdded game_event); + + /// + public event CourierItemAddedHandler CourierItemAdded = delegate { }; + + public delegate void CourierItemRemovedHandler(CourierItemRemoved game_event); + + /// + public event CourierItemRemovedHandler CourierItemRemoved = delegate { }; + + public delegate void CourierItemUpdatedHandler(CourierItemUpdated game_event); + + /// + public event CourierItemUpdatedHandler CourierItemUpdated = delegate { }; + + #endregion + + #region DraftEvents + + public delegate void DraftUpdatedHandler(DraftUpdated game_event); + + /// + public event DraftUpdatedHandler DraftUpdated = delegate { }; + + public delegate void TeamDraftDetailsUpdatedHandler(TeamDraftDetailsUpdated game_event); + + /// + public event TeamDraftDetailsUpdatedHandler TeamDraftDetailsUpdated = delegate { }; + + #endregion + + #region GameplayEvents + + public delegate void EventsUpdatedHandler(EventsUpdated game_event); + + /// + public event EventsUpdatedHandler EventsUpdated = delegate { }; + + public delegate void GameplayEventHandler(GameplayEvent game_event); + + /// + public event GameplayEventHandler GameplayEvent = delegate { }; + + public delegate void TeamGameplayEventHandler(TeamGameplayEvent game_event); + + /// + public event TeamGameplayEventHandler TeamGameplayEvent = delegate { }; + + public delegate void PlayerGameplayEventHandler(PlayerGameplayEvent game_event); + + /// + public event PlayerGameplayEventHandler PlayerGameplayEvent = delegate { }; + + #endregion + + #region HeroEvents + + public delegate void HeroUpdatedHandler(HeroUpdated game_event); + + /// + public event HeroUpdatedHandler HeroUpdated = delegate { }; + + public delegate void HeroDetailsChangedHandler(HeroDetailsChanged game_event); + + /// + public event HeroDetailsChangedHandler HeroDetailsChanged = delegate { }; + + public delegate void HeroLevelChangedHandler(HeroLevelChanged game_event); + + /// + public event HeroLevelChangedHandler HeroLevelChanged = delegate { }; + + public delegate void HeroHealthChangedHandler(HeroHealthChanged game_event); + + /// + public event HeroHealthChangedHandler HeroHealthChanged = delegate { }; + + public delegate void HeroDiedHandler(HeroDied game_event); + + /// + public event HeroDiedHandler HeroDied = delegate { }; + + public delegate void HeroRespawnedHandler(HeroRespawned game_event); + + /// + public event HeroRespawnedHandler HeroRespawned = delegate { }; + + public delegate void HeroTookDamageHandler(HeroTookDamage game_event); + + /// + public event HeroTookDamageHandler HeroTookDamage = delegate { }; + + public delegate void HeroManaChangedHandler(HeroManaChanged game_event); + + /// + public event HeroManaChangedHandler HeroManaChanged = delegate { }; + + public delegate void HeroStateChangedHandler(HeroStateChanged game_event); + + /// + public event HeroStateChangedHandler HeroStateChanged = delegate { }; + + public delegate void HeroMuteStateChangedHandler(HeroMuteStateChanged game_event); + + /// + public event HeroMuteStateChangedHandler HeroMuteStateChanged = delegate { }; + + public delegate void HeroSelectedChangedHandler(HeroSelectedChanged game_event); + + /// + public event HeroSelectedChangedHandler HeroSelectedChanged = delegate { }; + + public delegate void HeroTalentTreeChangedHandler(HeroTalentTreeChanged game_event); + + /// + public event HeroTalentTreeChangedHandler HeroTalentTreeChanged = delegate { }; + + public delegate void HeroAttributesLevelChangedHandler(HeroAttributesLevelChanged game_event); + + /// + public event HeroAttributesLevelChangedHandler HeroAttributesLevelChanged = delegate { }; + + #endregion + + #region ItemsEvents + + public delegate void ItemsUpdatedHandler(ItemsUpdated game_event); + + /// + public event ItemsUpdatedHandler ItemsUpdated = delegate { }; + + public delegate void ItemDetailsChangedHandler(ItemDetailsChanged game_event); + + /// + public event ItemDetailsChangedHandler ItemDetailsChanged = delegate { }; + + public delegate void ItemUpdatedHandler(ItemUpdated game_event); + + /// + public event ItemUpdatedHandler ItemUpdated = delegate { }; + + public delegate void InventoryItemAddedHandler(InventoryItemAdded game_event); + + /// + public event InventoryItemAddedHandler InventoryItemAdded = delegate { }; + + public delegate void InventoryItemRemovedHandler(InventoryItemRemoved game_event); + + /// + public event InventoryItemRemovedHandler InventoryItemRemoved = delegate { }; + + public delegate void InventoryItemUpdatedHandler(InventoryItemUpdated game_event); + + /// + public event InventoryItemUpdatedHandler InventoryItemUpdated = delegate { }; + + public delegate void StashItemAddedHandler(StashItemAdded game_event); + + /// + public event StashItemAddedHandler StashItemAdded = delegate { }; + + public delegate void StashItemRemovedHandler(StashItemRemoved game_event); + + /// + public event StashItemRemovedHandler StashItemRemoved = delegate { }; + + public delegate void StashItemUpdatedHandler(StashItemUpdated game_event); + + /// + public event StashItemUpdatedHandler StashItemUpdated = delegate { }; + + #endregion + + #region LeagueEvents + + public delegate void LeagueUpdatedHandler(LeagueUpdated game_event); + + /// + public event LeagueUpdatedHandler LeagueUpdated = delegate { }; + + #endregion + + #region MapEvents + + public delegate void MapUpdatedHandler(MapUpdated game_event); + + /// + public event MapUpdatedHandler MapUpdated = delegate { }; + + public delegate void TimeOfDayChangedHandler(TimeOfDayChanged game_event); + + /// + public event TimeOfDayChangedHandler TimeOfDayChanged = delegate { }; + + public delegate void TeamScoreChangedHandler(TeamScoreChanged game_event); + + /// + public event TeamScoreChangedHandler TeamScoreChanged = delegate { }; + + public delegate void GameStateChangedHandler(GameStateChanged game_event); + + /// + public event GameStateChangedHandler GameStateChanged = delegate { }; + + public delegate void PauseStateChangedHandler(PauseStateChanged game_event); + + /// + public event PauseStateChangedHandler PauseStateChanged = delegate { }; + + public delegate void GamePausedHandler(GamePaused game_event); + + /// + public event GamePausedHandler GamePaused = delegate { }; + + public delegate void GameResumedHandler(GameResumed game_event); + + /// + public event GameResumedHandler GameResumed = delegate { }; + + public delegate void TeamVictoryHandler(TeamVictory game_event); + + /// + public event TeamVictoryHandler TeamVictory = delegate { }; + + public delegate void TeamDefeatHandler(TeamDefeat game_event); + + /// + public event TeamDefeatHandler TeamDefeat = delegate { }; + + public delegate void RoshanStateChangedHandler(RoshanStateChanged game_event); + + /// + public event RoshanStateChangedHandler RoshanStateChanged = delegate { }; + + #endregion + + #region MinimapEvents + + public delegate void MinimapUpdatedHandler(MinimapUpdated game_event); + + /// + public event MinimapUpdatedHandler MinimapUpdated = delegate { }; + + public delegate void MinimapElementAddedHandler(MinimapElementAdded game_event); + + /// + public event MinimapElementAddedHandler MinimapElementAdded = delegate { }; + + public delegate void MinimapElementUpdatedHandler(MinimapElementUpdated game_event); + + /// + public event MinimapElementUpdatedHandler MinimapElementUpdated = delegate { }; + + public delegate void MinimapElementRemovedHandler(MinimapElementRemoved game_event); + + /// + public event MinimapElementRemovedHandler MinimapElementRemoved = delegate { }; + + public delegate void TeamMinimapElementUpdatedHandler(TeamMinimapElementUpdated game_event); + + /// + public event TeamMinimapElementUpdatedHandler TeamMinimapElementUpdated = delegate { }; + + #endregion + + #region NeutralItemsEvents + + public delegate void NeutralItemsUpdatedHandler(NeutralItemsUpdated game_event); + + /// + public event NeutralItemsUpdatedHandler NeutralItemsUpdated = delegate { }; + + public delegate void TeamNeutralItemsUpdatedHandler(TeamNeutralItemsUpdated game_event); + + /// + public event TeamNeutralItemsUpdatedHandler TeamNeutralItemsUpdated = delegate { }; + + #endregion + + #region PlayerEvents + + public delegate void PlayerUpdatedHandler(PlayerUpdated game_event); + + /// + public event PlayerUpdatedHandler PlayerUpdated = delegate { }; + + public delegate void PlayerDetailsChangedHandler(PlayerDetailsChanged game_event); + + /// + public event PlayerDetailsChangedHandler PlayerDetailsChanged = delegate { }; + + public delegate void PlayerKillsChangedHandler(PlayerKillsChanged game_event); + + /// + public event PlayerKillsChangedHandler PlayerKillsChanged = delegate { }; + + public delegate void PlayerDeathsChangedHandler(PlayerDeathsChanged game_event); + + /// + public event PlayerDeathsChangedHandler PlayerDeathsChanged = delegate { }; + + public delegate void PlayerAssistsChangedHandler(PlayerAssistsChanged game_event); + + /// + public event PlayerAssistsChangedHandler PlayerAssistsChanged = delegate { }; + + public delegate void PlayerLastHitsChangedHandler(PlayerLastHitsChanged game_event); + + /// + public event PlayerLastHitsChangedHandler PlayerLastHitsChanged = delegate { }; + + public delegate void PlayerDeniesChangedHandler(PlayerDeniesChanged game_event); + + /// + public event PlayerDeniesChangedHandler PlayerDeniesChanged = delegate { }; + + public delegate void PlayerKillStreakChangedHandler(PlayerKillStreakChanged game_event); + + /// + public event PlayerKillStreakChangedHandler PlayerKillStreakChanged = delegate { }; + + public delegate void PlayerGoldChangedHandler(PlayerGoldChanged game_event); + + /// + public event PlayerGoldChangedHandler PlayerGoldChanged = delegate { }; + + public delegate void PlayerWardsPurchasedChangedHandler(PlayerWardsPurchasedChanged game_event); + + /// + public event PlayerWardsPurchasedChangedHandler PlayerWardsPurchasedChanged = delegate { }; + + public delegate void PlayerWardsPlacedChangedHandler(PlayerWardsPlacedChanged game_event); + + /// + public event PlayerWardsPlacedChangedHandler PlayerWardsPlacedChanged = delegate { }; + + public delegate void PlayerWardsDestroyedChangedHandler(PlayerWardsDestroyedChanged game_event); + + /// + public event PlayerWardsDestroyedChangedHandler PlayerWardsDestroyedChanged = delegate { }; + + public delegate void PlayerRunesActivatedChangedHandler(PlayerRunesActivatedChanged game_event); + + /// + public event PlayerRunesActivatedChangedHandler PlayerRunesActivatedChanged = delegate { }; + + public delegate void PlayerCampsStackedChangedHandler(PlayerCampsStackedChanged game_event); + + /// + public event PlayerCampsStackedChangedHandler PlayerCampsStackedChanged = delegate { }; + + #endregion + + #region ProviderEvents + + public delegate void ProviderUpdatedHandler(ProviderUpdated game_event); + + /// + public event ProviderUpdatedHandler ProviderUpdated = delegate { }; + + #endregion + + #region RoshanEvents + + public delegate void RoshanUpdatedHandler(RoshanUpdated game_event); + + /// + public event RoshanUpdatedHandler RoshanUpdated = delegate { }; + + #endregion + + #region WearablesEvents + + public delegate void WearablesUpdatedHandler(WearablesUpdated game_event); + + /// + public event WearablesUpdatedHandler WearablesUpdated = delegate { }; + + public delegate void PlayerWearablesUpdatedHandler(PlayerWearablesUpdated game_event); + + /// + public event PlayerWearablesUpdatedHandler PlayerWearablesUpdated = delegate { }; + + #endregion + + public Dota2EventsInterface() + { + } + + internal override void OnNewGameEvent(DotaGameEvent e) + { + base.OnNewGameEvent(e); + + if (e is AbilitiesUpdated) + { + RaiseEvent(AbilitiesUpdated, e); + } + + if (e is AbilityDetailsChanged) + { + RaiseEvent(AbilityDetailsChanged, e); + } + + if (e is AbilityAdded) + { + RaiseEvent(AbilityAdded, e); + } + + if (e is AbilityRemoved) + { + RaiseEvent(AbilityRemoved, e); + } + + if (e is AbilityUpdated) + { + RaiseEvent(AbilityUpdated, e); + } + + if (e is AuthUpdated) + { + RaiseEvent(AuthUpdated, e); + } + + if (e is BuildingsUpdated) + { + RaiseEvent(BuildingsUpdated, e); + } + + if (e is BuildingsLayoutUpdated) + { + RaiseEvent(BuildingsLayoutUpdated, e); + } + + if (e is BuildingUpdated) + { + RaiseEvent(BuildingUpdated, e); + } + + if (e is TeamBuildingUpdated) + { + RaiseEvent(TeamBuildingUpdated, e); + } + + if (e is TeamBuildingDestroyed) + { + RaiseEvent(TeamBuildingDestroyed, e); + } + + if (e is TowerUpdated) + { + RaiseEvent(TowerUpdated, e); + } + + if (e is TowerDestroyed) + { + RaiseEvent(TowerDestroyed, e); + } + + if (e is RacksUpdated) + { + RaiseEvent(RacksUpdated, e); + } + + if (e is RacksDestroyed) + { + RaiseEvent(RacksDestroyed, e); + } + + if (e is AncientUpdated) + { + RaiseEvent(AncientUpdated, e); + } + + if (e is AncientDestroyed) + { + RaiseEvent(AncientDestroyed, e); + } + + if (e is CouriersUpdated) + { + RaiseEvent(CouriersUpdated, e); + } + + if (e is CourierUpdated) + { + RaiseEvent(CourierUpdated, e); + } + + if (e is TeamCourierUpdated) + { + RaiseEvent(TeamCourierUpdated, e); + } + + if (e is CourierItemAdded) + { + RaiseEvent(CourierItemAdded, e); + } + + if (e is CourierItemRemoved) + { + RaiseEvent(CourierItemRemoved, e); + } + + if (e is CourierItemUpdated) + { + RaiseEvent(CourierItemUpdated, e); + } + + if (e is DraftUpdated) + { + RaiseEvent(DraftUpdated, e); + } + + if (e is TeamDraftDetailsUpdated) + { + RaiseEvent(TeamDraftDetailsUpdated, e); + } + + if (e is EventsUpdated) + { + RaiseEvent(EventsUpdated, e); + } + + if (e is GameplayEvent) + { + RaiseEvent(GameplayEvent, e); + } + + if (e is TeamGameplayEvent) + { + RaiseEvent(TeamGameplayEvent, e); + } + + if (e is PlayerGameplayEvent) + { + RaiseEvent(PlayerGameplayEvent, e); + } + + if (e is HeroUpdated) + { + RaiseEvent(HeroUpdated, e); + } + + if (e is HeroDetailsChanged) + { + RaiseEvent(HeroDetailsChanged, e); + } + + if (e is HeroLevelChanged) + { + RaiseEvent(HeroLevelChanged, e); + } + + if (e is HeroHealthChanged) + { + RaiseEvent(HeroHealthChanged, e); + } + + if (e is HeroDied) + { + RaiseEvent(HeroDied, e); + } + + if (e is HeroRespawned) + { + RaiseEvent(HeroRespawned, e); + } + + if (e is HeroTookDamage) + { + RaiseEvent(HeroTookDamage, e); + } + + if (e is HeroManaChanged) + { + RaiseEvent(HeroManaChanged, e); + } + + if (e is HeroStateChanged) + { + RaiseEvent(HeroStateChanged, e); + } + + if (e is HeroMuteStateChanged) + { + RaiseEvent(HeroMuteStateChanged, e); + } + + if (e is HeroSelectedChanged) + { + RaiseEvent(HeroSelectedChanged, e); + } + + if (e is HeroTalentTreeChanged) + { + RaiseEvent(HeroTalentTreeChanged, e); + } + + if (e is HeroAttributesLevelChanged) + { + RaiseEvent(HeroAttributesLevelChanged, e); + } + + if (e is ItemsUpdated) + { + RaiseEvent(ItemsUpdated, e); + } + + if (e is ItemDetailsChanged) + { + RaiseEvent(ItemDetailsChanged, e); + } + + if (e is ItemUpdated) + { + RaiseEvent(ItemUpdated, e); + } + + if (e is InventoryItemAdded) + { + RaiseEvent(InventoryItemAdded, e); + } + + if (e is InventoryItemRemoved) + { + RaiseEvent(InventoryItemRemoved, e); + } + + if (e is InventoryItemUpdated) + { + RaiseEvent(InventoryItemUpdated, e); + } + + if (e is StashItemAdded) + { + RaiseEvent(StashItemAdded, e); + } + + if (e is StashItemRemoved) + { + RaiseEvent(StashItemRemoved, e); + } + + if (e is StashItemUpdated) + { + RaiseEvent(StashItemUpdated, e); + } + + if (e is LeagueUpdated) + { + RaiseEvent(LeagueUpdated, e); + } + + if (e is MapUpdated) + { + RaiseEvent(MapUpdated, e); + } + + if (e is TimeOfDayChanged) + { + RaiseEvent(TimeOfDayChanged, e); + } + + if (e is TeamScoreChanged) + { + RaiseEvent(TeamScoreChanged, e); + } + + if (e is GameStateChanged) + { + RaiseEvent(GameStateChanged, e); + } + + if (e is PauseStateChanged) + { + RaiseEvent(PauseStateChanged, e); + } + + if (e is GamePaused) + { + RaiseEvent(GamePaused, e); + } + + if (e is GameResumed) + { + RaiseEvent(GameResumed, e); + } + + if (e is TeamVictory) + { + RaiseEvent(TeamVictory, e); + } + + if (e is TeamDefeat) + { + RaiseEvent(TeamDefeat, e); + } + + if (e is RoshanStateChanged) + { + RaiseEvent(RoshanStateChanged, e); + } + + if (e is MinimapUpdated) + { + RaiseEvent(MinimapUpdated, e); + } + + if (e is MinimapElementAdded) + { + RaiseEvent(MinimapElementAdded, e); + } + + if (e is MinimapElementUpdated) + { + RaiseEvent(MinimapElementUpdated, e); + } + + if (e is MinimapElementRemoved) + { + RaiseEvent(MinimapElementRemoved, e); + } + + if (e is TeamMinimapElementUpdated) + { + RaiseEvent(TeamMinimapElementUpdated, e); + } + + if (e is NeutralItemsUpdated) + { + RaiseEvent(NeutralItemsUpdated, e); + } + + if (e is TeamNeutralItemsUpdated) + { + RaiseEvent(TeamNeutralItemsUpdated, e); + } + + if (e is PlayerUpdated) + { + RaiseEvent(PlayerUpdated, e); + } + + if (e is PlayerDetailsChanged) + { + RaiseEvent(PlayerDetailsChanged, e); + } + + if (e is PlayerKillsChanged) + { + RaiseEvent(PlayerKillsChanged, e); + } + + if (e is PlayerDeathsChanged) + { + RaiseEvent(PlayerDeathsChanged, e); + } + + if (e is PlayerAssistsChanged) + { + RaiseEvent(PlayerAssistsChanged, e); + } + + if (e is PlayerLastHitsChanged) + { + RaiseEvent(PlayerLastHitsChanged, e); + } + + if (e is PlayerDeniesChanged) + { + RaiseEvent(PlayerDeniesChanged, e); + } + + if (e is PlayerKillStreakChanged) + { + RaiseEvent(PlayerKillStreakChanged, e); + } + + if (e is PlayerGoldChanged) + { + RaiseEvent(PlayerGoldChanged, e); + } + + if (e is PlayerWardsPurchasedChanged) + { + RaiseEvent(PlayerWardsPurchasedChanged, e); + } + + if (e is PlayerWardsPlacedChanged) + { + RaiseEvent(PlayerWardsPlacedChanged, e); + } + + if (e is PlayerWardsDestroyedChanged) + { + RaiseEvent(PlayerWardsDestroyedChanged, e); + } + + if (e is PlayerRunesActivatedChanged) + { + RaiseEvent(PlayerRunesActivatedChanged, e); + } + + if (e is PlayerCampsStackedChanged) + { + RaiseEvent(PlayerCampsStackedChanged, e); + } + + if (e is ProviderUpdated) + { + RaiseEvent(ProviderUpdated, e); + } + + if (e is RoshanUpdated) + { + RaiseEvent(RoshanUpdated, e); + } + + if (e is WearablesUpdated) + { + RaiseEvent(WearablesUpdated, e); + } + + if (e is PlayerWearablesUpdated) + { + RaiseEvent(PlayerWearablesUpdated, e); + } + } + + } +} diff --git a/Dota2GSI/EventDispatcher.cs b/Dota2GSI/EventDispatcher.cs new file mode 100644 index 0000000..4089f44 --- /dev/null +++ b/Dota2GSI/EventDispatcher.cs @@ -0,0 +1,149 @@ +using Dota2GSI.EventMessages; +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Dota2GSI +{ + public class EventDispatcher where T : BaseEvent + { + /// + /// Delegate for handing game events. + /// + /// The new game event. + public delegate void GameEventHandler(T game_event); + + /// + /// Event for handing game events. + /// + public event GameEventHandler GameEvent = delegate { }; + + private readonly object subscriptions_lock = new object(); + + private Dictionary>> subscriptions = new Dictionary>>(); + private Dictionary>> pre_processors = new Dictionary>>(); + + public EventDispatcher() + { + Subscribe(RaiseOnGameEventHandler); + } + + ~EventDispatcher() + { + Unsubscribe(RaiseOnGameEventHandler); + } + + private void RaiseOnGameEventHandler(T game_event) + { + foreach (Delegate d in GameEvent.GetInvocationList()) + { + if (d.Target is ISynchronizeInvoke) + { + (d.Target as ISynchronizeInvoke).BeginInvoke(d, new object[] { game_event }); + } + else + { + d.DynamicInvoke(game_event); + } + } + } + + public void RegisterPreProcessor(Func callback) where MessageType : T + { + var event_type = typeof(MessageType); + + lock (subscriptions_lock) + { + if (!pre_processors.ContainsKey(event_type)) + { + pre_processors.Add(event_type, new HashSet>()); + } + + pre_processors[event_type].Add(callback); + } + } + + public void UnregisterPreProcessor(Func callback) where MessageType : T + { + var event_type = typeof(MessageType); + + lock (subscriptions_lock) + { + if (!subscriptions.ContainsKey(event_type)) + { + return; + } + + pre_processors[event_type].Remove(callback); + } + } + + + public void Subscribe(Action callback) where MessageType : T + { + var event_type = typeof(MessageType); + + lock (subscriptions_lock) + { + if (!subscriptions.ContainsKey(event_type)) + { + subscriptions.Add(event_type, new HashSet>()); + } + + subscriptions[event_type].Add(callback); + } + } + + public void Unsubscribe(Action callback) where MessageType : T + { + var event_type = typeof(MessageType); + + lock (subscriptions_lock) + { + if (!subscriptions.ContainsKey(event_type)) + { + return; + } + + subscriptions[event_type].Remove(callback); + } + } + + public void Broadcast(MessageType message) where MessageType : T + { + var event_type = typeof(MessageType); + T msg = message; + + lock (subscriptions_lock) + { + if (subscriptions.ContainsKey(event_type)) + { + // Run pre-processors first + if (pre_processors.ContainsKey(event_type)) + { + foreach (var pre_processor in pre_processors[event_type]) + { + msg = pre_processor(msg); + + if (msg == null) + { + // The message was handled. + return; + } + } + } + + foreach (var callback in subscriptions[event_type]) + { + callback.Invoke(msg); + } + } + + if (event_type != typeof(T)) + { + Broadcast(msg); + } + } + } + } +} diff --git a/Dota2GSI/EventHandler.cs b/Dota2GSI/EventHandler.cs new file mode 100644 index 0000000..8be4515 --- /dev/null +++ b/Dota2GSI/EventHandler.cs @@ -0,0 +1,14 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class EventHandler where T : BaseEvent + { + protected EventDispatcher dispatcher = new EventDispatcher(); + + public EventHandler(ref EventDispatcher EventDispatcher) + { + dispatcher = EventDispatcher; + } + } +} diff --git a/Dota2GSI/EventMessages/AbilitiesEvents.cs b/Dota2GSI/EventMessages/AbilitiesEvents.cs new file mode 100644 index 0000000..2927de1 --- /dev/null +++ b/Dota2GSI/EventMessages/AbilitiesEvents.cs @@ -0,0 +1,56 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.AbilitiesProvider; +using Dota2GSI.Nodes.Helpers; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Hero Abilities update. + /// + public class AbilitiesUpdated : UpdateEvent + { + public AbilitiesUpdated(Abilities new_value, Abilities previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific player's Hero Ability Details change. + /// + public class AbilityDetailsChanged : PlayerUpdateEvent + { + public AbilityDetailsChanged(AbilityDetails new_value, AbilityDetails previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero Ability addition. + /// + public class AbilityAdded : PlayerValueEvent + { + public AbilityAdded(Ability value, FullPlayerDetails player) : base(value, player) + { + } + } + + /// + /// Event for specific player's Hero Ability removal. + /// + public class AbilityRemoved : PlayerValueEvent + { + public AbilityRemoved(Ability value, FullPlayerDetails player) : base(value, player) + { + } + } + + /// + /// Event for specific player's Hero Ability update. + /// + public class AbilityUpdated : PlayerUpdateEvent + { + public AbilityUpdated(Ability new_value, Ability previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } +} diff --git a/Dota2GSI/EventMessages/AuthEvents.cs b/Dota2GSI/EventMessages/AuthEvents.cs new file mode 100644 index 0000000..104c317 --- /dev/null +++ b/Dota2GSI/EventMessages/AuthEvents.cs @@ -0,0 +1,14 @@ +using Dota2GSI.Nodes; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Auth update. + /// + public class AuthUpdated : UpdateEvent + { + public AuthUpdated(Auth new_value, Auth previous_value) : base(new_value, previous_value) + { + } + } +} diff --git a/Dota2GSI/EventMessages/BaseEvent.cs b/Dota2GSI/EventMessages/BaseEvent.cs new file mode 100644 index 0000000..911618a --- /dev/null +++ b/Dota2GSI/EventMessages/BaseEvent.cs @@ -0,0 +1,6 @@ +namespace Dota2GSI.EventMessages +{ + public interface BaseEvent + { + } +} diff --git a/Dota2GSI/EventMessages/BuildingsEvents.cs b/Dota2GSI/EventMessages/BuildingsEvents.cs new file mode 100644 index 0000000..7741fce --- /dev/null +++ b/Dota2GSI/EventMessages/BuildingsEvents.cs @@ -0,0 +1,167 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.BuildingsProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Buildings update. + /// + public class BuildingsUpdated : UpdateEvent + { + public BuildingsUpdated(Buildings new_value, Buildings previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific team's Building Layout update. + /// + public class BuildingsLayoutUpdated : TeamUpdateEvent + { + public BuildingsLayoutUpdated(BuildingLayout new_value, BuildingLayout previous_value, PlayerTeam team) : base(new_value, previous_value, team) + { + } + } + + /// + /// Enum for building locations. + /// + public enum BuildingLocation + { + /// + /// Undefined. + /// + Undefined, + + /// + /// Top lane building. + /// + TopLane, + + /// + /// Middle lane building. + /// + MiddleLane, + + /// + /// Bottom lane building. + /// + BottomLane, + + /// + /// Base building. + /// + Base + } + + /// + /// Event for specific Building update. + /// + public class BuildingUpdated : EntityUpdateEvent + { + public BuildingUpdated(Building new_value, Building previous_value, string entity_id) : base(new_value, previous_value, entity_id) + { + } + } + + /// + /// Event for specific team's Building update. + /// + public class TeamBuildingUpdated : BuildingUpdated + { + public readonly PlayerTeam Team; + + /// + /// The location of this building. + /// + public readonly BuildingLocation Location; + + public TeamBuildingUpdated(Building new_value, Building previous_value, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id) + { + Team = team; + Location = location; + } + } + + /// + /// Event for specific team's Building destruction. + /// + public class TeamBuildingDestroyed : TeamBuildingUpdated + { + public TeamBuildingDestroyed(Building new_value, Building previous_value, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id, team, location) + { + } + } + + /// + /// Event for specific team's Tower update. + /// + public class TowerUpdated : TeamBuildingUpdated + { + public TowerUpdated(Building new_value, Building previous_value, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id, team, location) + { + } + } + + /// + /// Event for specific team's Tower destruction. + /// + public class TowerDestroyed : TeamBuildingDestroyed + { + public TowerDestroyed(Building new_value, Building previous_value, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id, team, location) + { + } + } + + /// + /// Event for specific team's Racks update. + /// + public class RacksUpdated : TeamBuildingUpdated + { + /// + /// The affected racks type. + /// + public readonly RacksType RacksType; + + public RacksUpdated(Building new_value, Building previous_value, RacksType racks_type, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id, team, location) + { + RacksType = racks_type; + } + } + + /// + /// Event for specific team's Racks destruction. + /// + public class RacksDestroyed : TeamBuildingDestroyed + { + /// + /// The affected racks type. + /// + public readonly RacksType RacksType; + + public RacksDestroyed(Building new_value, Building previous_value, RacksType racks_type, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id, team, location) + { + RacksType = racks_type; + } + } + + /// + /// Event for specific team's Ancient update. + /// + public class AncientUpdated : TeamBuildingUpdated + { + public AncientUpdated(Building new_value, Building previous_value, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id, team, location) + { + } + } + + /// + /// Event for specific team's Ancient destruction. + /// + public class AncientDestroyed : TeamBuildingDestroyed + { + public AncientDestroyed(Building new_value, Building previous_value, string entity_id, PlayerTeam team, BuildingLocation location) : base(new_value, previous_value, entity_id, team, location) + { + } + } +} diff --git a/Dota2GSI/EventMessages/CouriersEvents.cs b/Dota2GSI/EventMessages/CouriersEvents.cs new file mode 100644 index 0000000..f27b062 --- /dev/null +++ b/Dota2GSI/EventMessages/CouriersEvents.cs @@ -0,0 +1,84 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.CouriersProvider; +using Dota2GSI.Nodes.Helpers; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Couriers update. + /// + public class CouriersUpdated : UpdateEvent + { + public CouriersUpdated(Couriers new_value, Couriers previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific player's Courier update. + /// + public class CourierUpdated : PlayerUpdateEvent + { + public CourierUpdated(Courier new_value, Courier previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific team's Courier update. + /// + public class TeamCourierUpdated : TeamUpdateEvent + { + public TeamCourierUpdated(Courier new_value, Courier previous_value, PlayerTeam team) : base(new_value, previous_value, team) + { + } + } + + /// + /// Event for specific player's courier gaining an item in their inventory. + /// + public class CourierItemAdded : PlayerValueEvent + { + /// + /// The player's courier. + /// + public readonly Courier Courier; + + public CourierItemAdded(CourierItem value, Courier courier, FullPlayerDetails player) : base(value, player) + { + Courier = courier; + } + } + + /// + /// Event for specific player's courier losing an item from their inventory. + /// + public class CourierItemRemoved : PlayerValueEvent + { + /// + /// The player's courier. + /// + public readonly Courier Courier; + + public CourierItemRemoved(CourierItem value, Courier courier, FullPlayerDetails player) : base(value, player) + { + Courier = courier; + } + } + + /// + /// Event for specific player's courier item updating in their inventory. + /// + public class CourierItemUpdated : PlayerUpdateEvent + { + /// + /// The player's courier. + /// + public readonly Courier Courier; + + public CourierItemUpdated(CourierItem new_value, CourierItem previous_value, Courier courier, FullPlayerDetails player) : base(new_value, previous_value, player) + { + Courier = courier; + } + } +} diff --git a/Dota2GSI/EventMessages/DotaGameEvent.cs b/Dota2GSI/EventMessages/DotaGameEvent.cs new file mode 100644 index 0000000..6763596 --- /dev/null +++ b/Dota2GSI/EventMessages/DotaGameEvent.cs @@ -0,0 +1,9 @@ +namespace Dota2GSI.EventMessages +{ + public class DotaGameEvent : BaseEvent + { + public DotaGameEvent() : base() + { + } + } +} diff --git a/Dota2GSI/EventMessages/DraftEvents.cs b/Dota2GSI/EventMessages/DraftEvents.cs new file mode 100644 index 0000000..71a722a --- /dev/null +++ b/Dota2GSI/EventMessages/DraftEvents.cs @@ -0,0 +1,25 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.DraftProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Draft update. + /// + public class DraftUpdated : UpdateEvent + { + public DraftUpdated(Draft new_value, Draft previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific team's Draft Details update. + /// + public class TeamDraftDetailsUpdated : TeamUpdateEvent + { + public TeamDraftDetailsUpdated(DraftDetails new_value, DraftDetails previous_value, PlayerTeam team) : base(new_value, previous_value, team) + { + } + } +} diff --git a/Dota2GSI/EventMessages/FullDetailsEvents.cs b/Dota2GSI/EventMessages/FullDetailsEvents.cs new file mode 100644 index 0000000..4d1c403 --- /dev/null +++ b/Dota2GSI/EventMessages/FullDetailsEvents.cs @@ -0,0 +1,44 @@ +using Dota2GSI.Nodes.Helpers; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Full Player Details update. + /// + public class FullPlayerDetailsUpdated : UpdateEvent + { + public FullPlayerDetailsUpdated(FullPlayerDetails new_value, FullPlayerDetails previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for overall Full Team Details update. + /// + public class FullTeamDetailsUpdated : UpdateEvent + { + public FullTeamDetailsUpdated(FullTeamDetails new_value, FullTeamDetails previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for Player Disconnected event. + /// + public class PlayerDisconnected : ValueEvent + { + public PlayerDisconnected(FullPlayerDetails value) : base(value) + { + } + } + + /// + /// Event for Player Connected event. + /// + public class PlayerConnected : ValueEvent + { + public PlayerConnected(FullPlayerDetails value) : base(value) + { + } + } +} diff --git a/Dota2GSI/EventMessages/GameplayEvents.cs b/Dota2GSI/EventMessages/GameplayEvents.cs new file mode 100644 index 0000000..2da2121 --- /dev/null +++ b/Dota2GSI/EventMessages/GameplayEvents.cs @@ -0,0 +1,46 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.EventsProvider; +using Dota2GSI.Nodes.Helpers; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Events update. + /// + public class EventsUpdated : UpdateEvent + { + public EventsUpdated(Events new_value, Events previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for Gameplay Event. + /// + public class GameplayEvent : ValueEvent + { + public GameplayEvent(Event value) : base(value) + { + } + } + + /// + /// Event for specific team's Gameplay Event. + /// + public class TeamGameplayEvent : TeamValueEvent + { + public TeamGameplayEvent(Event value, PlayerTeam team) : base(value, team) + { + } + } + + /// + /// Event for specific player's Event. + /// + public class PlayerGameplayEvent : PlayerValueEvent + { + public PlayerGameplayEvent(Event value, FullPlayerDetails player) : base(value, player) + { + } + } +} diff --git a/Dota2GSI/EventMessages/HeroEvents.cs b/Dota2GSI/EventMessages/HeroEvents.cs new file mode 100644 index 0000000..379d8fa --- /dev/null +++ b/Dota2GSI/EventMessages/HeroEvents.cs @@ -0,0 +1,136 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.Helpers; +using Dota2GSI.Nodes.HeroProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Hero update. + /// + public class HeroUpdated : UpdateEvent + { + public HeroUpdated(Hero new_value, Hero previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific player's Hero Details update. + /// + public class HeroDetailsChanged : PlayerUpdateEvent + { + public HeroDetailsChanged(HeroDetails new_value, HeroDetails previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero level update. + /// + public class HeroLevelChanged : PlayerUpdateEvent + { + public HeroLevelChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero health update. + /// + public class HeroHealthChanged : PlayerUpdateEvent + { + public HeroHealthChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero death. + /// + public class HeroDied : HeroHealthChanged + { + public HeroDied(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero respawn. + /// + public class HeroRespawned : HeroHealthChanged + { + public HeroRespawned(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero taking damage. + /// + public class HeroTookDamage : HeroHealthChanged + { + public HeroTookDamage(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero mana update. + /// + public class HeroManaChanged : PlayerUpdateEvent + { + public HeroManaChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero state update. + /// + public class HeroStateChanged : PlayerUpdateEvent + { + public HeroStateChanged(HeroState new_value, HeroState previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero mute update. + /// + public class HeroMuteStateChanged : PlayerUpdateEvent + { + public HeroMuteStateChanged(bool new_value, bool previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero selection update. + /// + public class HeroSelectedChanged : PlayerUpdateEvent + { + public HeroSelectedChanged(bool new_value, bool previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero Talent Tree update. + /// + public class HeroTalentTreeChanged : PlayerUpdateEvent + { + public HeroTalentTreeChanged(TalentTreeSpec[] new_value, TalentTreeSpec[] previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Hero Attributes Level update. + /// + public class HeroAttributesLevelChanged : PlayerUpdateEvent + { + public HeroAttributesLevelChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } +} diff --git a/Dota2GSI/EventMessages/ItemsEvents.cs b/Dota2GSI/EventMessages/ItemsEvents.cs new file mode 100644 index 0000000..04492df --- /dev/null +++ b/Dota2GSI/EventMessages/ItemsEvents.cs @@ -0,0 +1,96 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.Helpers; +using Dota2GSI.Nodes.ItemsProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Items update. + /// + public class ItemsUpdated : UpdateEvent + { + public ItemsUpdated(Items new_value, Items previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific player's Item Details update. + /// + public class ItemDetailsChanged : PlayerUpdateEvent + { + public ItemDetailsChanged(ItemDetails new_value, ItemDetails previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Item update. + /// + public class ItemUpdated : PlayerUpdateEvent + { + public ItemUpdated(Item new_value, Item previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player gaining an item in their inventory. + /// + public class InventoryItemAdded : PlayerValueEvent + { + public InventoryItemAdded(Item value, FullPlayerDetails player) : base(value, player) + { + } + } + + /// + /// Event for specific player losing an item from their inventory. + /// + public class InventoryItemRemoved : PlayerValueEvent + { + public InventoryItemRemoved(Item value, FullPlayerDetails player) : base(value, player) + { + } + } + + /// + /// Event for specific player's inventory item updating. + /// + public class InventoryItemUpdated : ItemUpdated + { + public InventoryItemUpdated(Item new_value, Item previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player gaining an item in their stash. + /// + public class StashItemAdded : PlayerValueEvent + { + public StashItemAdded(Item value, FullPlayerDetails player) : base(value, player) + { + } + } + + /// + /// Event for specific player losing an item from their stash. + /// + public class StashItemRemoved : PlayerValueEvent + { + public StashItemRemoved(Item value, FullPlayerDetails player) : base(value, player) + { + } + } + + /// + /// Event for specific player's stash item updating. + /// + public class StashItemUpdated : ItemUpdated + { + public StashItemUpdated(Item new_value, Item previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } +} diff --git a/Dota2GSI/EventMessages/LeagueEvents.cs b/Dota2GSI/EventMessages/LeagueEvents.cs new file mode 100644 index 0000000..a882cc4 --- /dev/null +++ b/Dota2GSI/EventMessages/LeagueEvents.cs @@ -0,0 +1,14 @@ +using Dota2GSI.Nodes; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall League update. + /// + public class LeagueUpdated : UpdateEvent + { + public LeagueUpdated(League new_value, League previous_value) : base(new_value, previous_value) + { + } + } +} diff --git a/Dota2GSI/EventMessages/MapEvents.cs b/Dota2GSI/EventMessages/MapEvents.cs new file mode 100644 index 0000000..395e46e --- /dev/null +++ b/Dota2GSI/EventMessages/MapEvents.cs @@ -0,0 +1,128 @@ +using Dota2GSI.Nodes; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Map update. + /// + public class MapUpdated : UpdateEvent + { + public MapUpdated(Map new_value, Map previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for Time of Day change. + /// + public class TimeOfDayChanged : DotaGameEvent + { + /// + /// True when day time, false when night time. + /// + public readonly bool IsDaytime; + + /// + /// True when nightstalker night, false otherwise. + /// + public readonly bool IsNightstalkerNight; + + public TimeOfDayChanged(bool is_day, bool is_nightstalker_night) : base() + { + IsDaytime = is_day; + IsNightstalkerNight = is_nightstalker_night; + } + } + + /// + /// Event for specific team's score change. + /// + public class TeamScoreChanged : TeamUpdateEvent + { + public TeamScoreChanged(int new_value, int previous_value, PlayerTeam team) : base(new_value, previous_value, team) + { + } + } + + /// + /// Event for Game State change. + /// + public class GameStateChanged : UpdateEvent + { + public GameStateChanged(DOTA_GameState new_value, DOTA_GameState previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for Game Pause State change. + /// + public class PauseStateChanged : UpdateEvent + { + public PauseStateChanged(bool new_value, bool previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for Game Paused. + /// + public class GamePaused : DotaGameEvent + { + public GamePaused() : base() + { + } + } + + /// + /// Event for Game Resumed. + /// + public class GameResumed : DotaGameEvent + { + public GameResumed() : base() + { + } + } + + /// + /// Event for specific team's Victory. + /// + public class TeamVictory : DotaGameEvent + { + /// + /// The winning team. + /// + public readonly PlayerTeam Team; + + public TeamVictory(PlayerTeam team) : base() + { + Team = team; + } + } + + /// + /// Event for specific team's Defeat. + /// + public class TeamDefeat : DotaGameEvent + { + /// + /// The losing team. + /// + public readonly PlayerTeam Team; + + public TeamDefeat(PlayerTeam team) : base() + { + Team = team; + } + } + + /// + /// Event for Roshan State change. + /// + public class RoshanStateChanged : UpdateEvent + { + public RoshanStateChanged(RoshanState new_value, RoshanState previous_value) : base(new_value, previous_value) + { + } + } +} diff --git a/Dota2GSI/EventMessages/MinimapEvents.cs b/Dota2GSI/EventMessages/MinimapEvents.cs new file mode 100644 index 0000000..3ed2ba9 --- /dev/null +++ b/Dota2GSI/EventMessages/MinimapEvents.cs @@ -0,0 +1,55 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.MinimapProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Minimap update. + /// + public class MinimapUpdated : UpdateEvent + { + public MinimapUpdated(Minimap new_value, Minimap previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for a Minimap Element addition. + /// + public class MinimapElementAdded : EntityValueEvent + { + public MinimapElementAdded(MinimapElement value, string entity_id) : base(value, entity_id) + { + } + } + + /// + /// Event for a Minimap Element update. + /// + public class MinimapElementUpdated : EntityUpdateEvent + { + public MinimapElementUpdated(MinimapElement new_value, MinimapElement previous_value, string entity_id) : base(new_value, previous_value, entity_id) + { + } + } + + /// + /// Event for a Minimap Element removal. + /// + public class MinimapElementRemoved : EntityValueEvent + { + public MinimapElementRemoved(MinimapElement value, string entity_id) : base(value, entity_id) + { + } + } + + /// + /// Event for specific team's Minimap Element update. + /// + public class TeamMinimapElementUpdated : TeamUpdateEvent + { + public TeamMinimapElementUpdated(MinimapElement new_value, MinimapElement previous_value, PlayerTeam team) : base(new_value, previous_value, team) + { + } + } +} diff --git a/Dota2GSI/EventMessages/NeutralItemsEvents.cs b/Dota2GSI/EventMessages/NeutralItemsEvents.cs new file mode 100644 index 0000000..d28cf74 --- /dev/null +++ b/Dota2GSI/EventMessages/NeutralItemsEvents.cs @@ -0,0 +1,25 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.NeutralItemsProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Neutral Items update. + /// + public class NeutralItemsUpdated : UpdateEvent + { + public NeutralItemsUpdated(NeutralItems new_value, NeutralItems previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific team's Neutral Items update. + /// + public class TeamNeutralItemsUpdated : TeamUpdateEvent + { + public TeamNeutralItemsUpdated(TeamNeutralItems new_value, TeamNeutralItems previous_value, PlayerTeam team) : base(new_value, previous_value, team) + { + } + } +} diff --git a/Dota2GSI/EventMessages/PlayerEvents.cs b/Dota2GSI/EventMessages/PlayerEvents.cs new file mode 100644 index 0000000..08957fb --- /dev/null +++ b/Dota2GSI/EventMessages/PlayerEvents.cs @@ -0,0 +1,146 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.Helpers; +using Dota2GSI.Nodes.PlayerProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Player update. + /// + public class PlayerUpdated : UpdateEvent + { + public PlayerUpdated(Player new_value, Player previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific player's Player Details update. + /// + public class PlayerDetailsChanged : PlayerUpdateEvent + { + public PlayerDetailsChanged(PlayerDetails new_value, PlayerDetails previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Kills count change. + /// + public class PlayerKillsChanged : PlayerUpdateEvent + { + public PlayerKillsChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Deaths count change. + /// + public class PlayerDeathsChanged : PlayerUpdateEvent + { + public PlayerDeathsChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Assists count change. + /// + public class PlayerAssistsChanged : PlayerUpdateEvent + { + public PlayerAssistsChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Last Hits count change. + /// + public class PlayerLastHitsChanged : PlayerUpdateEvent + { + public PlayerLastHitsChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Denies count change. + /// + public class PlayerDeniesChanged : PlayerUpdateEvent + { + public PlayerDeniesChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Kill Streak count change. + /// + public class PlayerKillStreakChanged : PlayerUpdateEvent + { + public PlayerKillStreakChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Gold count change. + /// + public class PlayerGoldChanged : PlayerUpdateEvent + { + public PlayerGoldChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Purchased Wards count change. + /// + public class PlayerWardsPurchasedChanged : PlayerUpdateEvent + { + public PlayerWardsPurchasedChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Placed Wards count change. + /// + public class PlayerWardsPlacedChanged : PlayerUpdateEvent + { + public PlayerWardsPlacedChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Destroyed Wards count change. + /// + public class PlayerWardsDestroyedChanged : PlayerUpdateEvent + { + public PlayerWardsDestroyedChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Runes Activated count change. + /// + public class PlayerRunesActivatedChanged : PlayerUpdateEvent + { + public PlayerRunesActivatedChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } + + /// + /// Event for specific player's Stacked Camps count change. + /// + public class PlayerCampsStackedChanged : PlayerUpdateEvent + { + public PlayerCampsStackedChanged(int new_value, int previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } +} diff --git a/Dota2GSI/EventMessages/ProviderEvents.cs b/Dota2GSI/EventMessages/ProviderEvents.cs new file mode 100644 index 0000000..91c1309 --- /dev/null +++ b/Dota2GSI/EventMessages/ProviderEvents.cs @@ -0,0 +1,14 @@ +using Dota2GSI.Nodes; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Provider update. + /// + public class ProviderUpdated : UpdateEvent + { + public ProviderUpdated(Provider new_value, Provider previous_value) : base(new_value, previous_value) + { + } + } +} diff --git a/Dota2GSI/EventMessages/RoshanEvents.cs b/Dota2GSI/EventMessages/RoshanEvents.cs new file mode 100644 index 0000000..41ae019 --- /dev/null +++ b/Dota2GSI/EventMessages/RoshanEvents.cs @@ -0,0 +1,14 @@ +using Dota2GSI.Nodes; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Roshan update. + /// + public class RoshanUpdated : UpdateEvent + { + public RoshanUpdated(Roshan new_value, Roshan previous_value) : base(new_value, previous_value) + { + } + } +} diff --git a/Dota2GSI/EventMessages/UpdateEvent.cs b/Dota2GSI/EventMessages/UpdateEvent.cs new file mode 100644 index 0000000..47bf68f --- /dev/null +++ b/Dota2GSI/EventMessages/UpdateEvent.cs @@ -0,0 +1,147 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.Helpers; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for a single value update. + /// + /// The value type. + public class ValueEvent : DotaGameEvent + { + /// + /// Value. + /// + public readonly T Value; + + public ValueEvent(T value) + { + Value = value; + } + } + + /// + /// Event for specific player's single value update. + /// + /// The value type. + public class PlayerValueEvent : ValueEvent + { + /// + /// The associated player details. + /// + public readonly FullPlayerDetails Player; + + public PlayerValueEvent(T value, FullPlayerDetails player) : base(value) + { + Player = player; + } + } + + /// + /// Event for specific team's single value update. + /// + /// The value type. + public class TeamValueEvent : ValueEvent + { + /// + /// The associated team. + /// + public readonly PlayerTeam Team; + + public TeamValueEvent(T value, PlayerTeam team) : base(value) + { + Team = team; + } + } + + /// + /// Event for value change. + /// + /// The value type. + public class UpdateEvent : DotaGameEvent + { + /// + /// New value. + /// + public readonly T New; + + /// + /// Previous value. + /// + public readonly T Previous; + + public UpdateEvent(T new_value, T previous_value) + { + New = new_value; + Previous = previous_value; + } + } + + /// + /// Event for specific player's value change. + /// + /// The value type. + public class PlayerUpdateEvent : UpdateEvent + { + /// + /// The associated player. + /// + public readonly FullPlayerDetails Player; + + public PlayerUpdateEvent(T new_value, T previous_value, FullPlayerDetails player) : base(new_value, previous_value) + { + Player = player; + } + } + + /// + /// Event for specific team's value change. + /// + /// + public class TeamUpdateEvent : UpdateEvent + { + /// + /// The associated team. + /// + public readonly PlayerTeam Team; + + public TeamUpdateEvent(T new_value, T previous_value, PlayerTeam team) : base(new_value, previous_value) + { + Team = team; + } + } + + /// + /// Event for specific entity's single value update. + /// + /// + public class EntityValueEvent : ValueEvent + { + /// + /// The associated entity ID. + /// + public readonly string EntityID; + + public EntityValueEvent(T value, string entity_id) : base(value) + { + EntityID = entity_id; + } + } + + /// + /// Event for specific entity's value change. + /// + /// + public class EntityUpdateEvent : UpdateEvent + { + /// + /// The associated entity ID. + /// + public readonly string EntityID; + + public EntityUpdateEvent(T new_value, T previous_value, string entity_id) : base(new_value, previous_value) + { + EntityID = entity_id; + } + } +} diff --git a/Dota2GSI/EventMessages/WearablesEvents.cs b/Dota2GSI/EventMessages/WearablesEvents.cs new file mode 100644 index 0000000..87a3bb8 --- /dev/null +++ b/Dota2GSI/EventMessages/WearablesEvents.cs @@ -0,0 +1,26 @@ +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.Helpers; +using Dota2GSI.Nodes.WearablesProvider; + +namespace Dota2GSI.EventMessages +{ + /// + /// Event for overall Wearables update. + /// + public class WearablesUpdated : UpdateEvent + { + public WearablesUpdated(Wearables new_value, Wearables previous_value) : base(new_value, previous_value) + { + } + } + + /// + /// Event for specific player's Wearables update. + /// + public class PlayerWearablesUpdated : PlayerUpdateEvent + { + public PlayerWearablesUpdated(PlayerWearables new_value, PlayerWearables previous_value, FullPlayerDetails player) : base(new_value, previous_value, player) + { + } + } +} diff --git a/Dota2GSI/EventsInterface.cs b/Dota2GSI/EventsInterface.cs new file mode 100644 index 0000000..06e4cd9 --- /dev/null +++ b/Dota2GSI/EventsInterface.cs @@ -0,0 +1,44 @@ +using Dota2GSI.EventMessages; +using System; +using System.ComponentModel; + +namespace Dota2GSI +{ + public class EventsInterface where T : BaseEvent + { + /// + /// Delegate for handing game events. + /// + /// The new game event. + public delegate void GameEventHandler(T game_event); + + /// + /// Event for handing GSI game events. + /// + public event GameEventHandler GameEvent = delegate { }; + + public EventsInterface() + { + } + + internal virtual void OnNewGameEvent(T e) + { + RaiseEvent(GameEvent, e); + } + + protected void RaiseEvent(MulticastDelegate multi_delegate, object obj) + { + foreach (Delegate d in multi_delegate.GetInvocationList()) + { + if (d.Target is ISynchronizeInvoke) + { + (d.Target as ISynchronizeInvoke).BeginInvoke(d, new object[] { obj }); + } + else + { + d.DynamicInvoke(obj); + } + } + } + } +} diff --git a/Dota2GSI/GameState.cs b/Dota2GSI/GameState.cs index c787fca..b801566 100644 --- a/Dota2GSI/GameState.cs +++ b/Dota2GSI/GameState.cs @@ -1,4 +1,4 @@ -using Dota2GSI.Nodes; +using Dota2GSI.Nodes; using Dota2GSI.Nodes.Helpers; using Newtonsoft.Json.Linq; @@ -9,312 +9,101 @@ namespace Dota2GSI /// public class GameState : Node { - private Auth auth; - private Provider provider; - private Map map; - private Player player; - private Hero hero; - private Abilities abilities; - private Items items; - private Events events; - private Buildings buildings; - private League league; - private Draft draft; - private Wearables wearables; - private Minimap minimap; - private Roshan roshan; - private Couriers couriers; - private NeutralItems neutral_items; - private GameState previously; - // private GameState added; // Added is removed due to only returning bool values instead of proper values. - - // Helpers - - private FullPlayerDetails local_player_details; - private FullTeamDetails radiant_team_details; - private FullTeamDetails dire_team_details; - private FullTeamDetails neutral_team_details; - - - /// - /// Creates a GameState instance based on the given json data. - /// - /// The parsed json data. - public GameState(JObject parsed_data = null) : base(parsed_data) - { - } - /// /// Information about GSI authentication.
/// Enabled by including "auth" "1" in the game state cfg file. ///
- public Auth Auth - { - get - { - if (auth == null) - { - auth = new Auth(GetJObject("auth")); - } - - return auth; - } - } + public readonly Auth Auth; /// /// Information about the provider of this GameState.
/// Enabled by including "provider" "1" in the game state cfg file. ///
- public Provider Provider - { - get - { - if (provider == null) - { - provider = new Provider(GetJObject("provider")); - } - - return provider; - } - } + public readonly Provider Provider; /// /// Information about the current map.
/// Enabled by including "map" "1" in the game state cfg file. ///
- public Map Map - { - get - { - if (map == null) - { - map = new Map(GetJObject("map")); - } - - return map; - } - } + public readonly Map Map; /// /// Information about the local player or team players when spectating.
/// Enabled by including "player" "1" in the game state cfg file. ///
- public Player Player - { - get - { - if (player == null) - { - player = new Player(GetJObject("player")); - } - - return player; - } - } + public readonly Player Player; /// /// Information about the local player's hero or team players heroes when spectating.
/// Enabled by including "hero" "1" in the game state cfg file. ///
- public Hero Hero - { - get - { - if (hero == null) - { - hero = new Hero(GetJObject("hero")); - } - - return hero; - } - } + public readonly Hero Hero; /// /// Information about the local player's hero abilities or team players abilities when spectating.
/// Enabled by including "abilities" "1" in the game state cfg file. ///
- public Abilities Abilities - { - get - { - if (abilities == null) - { - abilities = new Abilities(GetJObject("abilities")); - } - - return abilities; - } - } + public readonly Abilities Abilities; /// /// Information about the local player's hero items or team players items when spectating.
/// Enabled by including "items" "1" in the game state cfg file. ///
- public Items Items - { - get - { - if (items == null) - { - items = new Items(GetJObject("items")); - } - - return items; - } - } + public readonly Items Items; /// /// Information about game events.
/// Enabled by including "events" "1" in the game state cfg file. ///
- public Events Events - { - get - { - if (events == null) - { - events = new Events(GetJArray("events")); - } - - return events; - } - } + public readonly Events Events; /// /// Information about the buildings on the map.
/// Enabled by including "buildings" "1" in the game state cfg file. ///
- public Buildings Buildings - { - get - { - if (buildings == null) - { - buildings = new Buildings(GetJObject("buildings")); - } - - return buildings; - } - } + public readonly Buildings Buildings; /// /// Information about the current league (or game configuration).
/// Enabled by including "league" "1" in the game state cfg file. ///
- public League League - { - get - { - if (league == null) - { - league = new League(GetJObject("league")); - } - - return league; - } - } + public readonly League League; /// /// Information about the draft. (TOURNAMENT ONLY)
/// Enabled by including "draft" "1" in the game state cfg file. ///
- public Draft Draft - { - get - { - if (draft == null) - { - draft = new Draft(GetJObject("draft")); - } - - return draft; - } - } + public readonly Draft Draft; /// /// Information about the local player's wearable items or team players wearable items when spectating.
/// Enabled by including "wearables" "1" in the game state cfg file. ///
- public Wearables Wearables - { - get - { - if (wearables == null) - { - wearables = new Wearables(GetJObject("wearables")); - } - - return wearables; - } - } + public readonly Wearables Wearables; /// /// Information about the minimap.
/// Enabled by including "minimap" "1" in the game state cfg file. ///
- public Minimap Minimap - { - get - { - if (minimap == null) - { - minimap = new Minimap(GetJObject("minimap")); - } - - return minimap; - } - } + public readonly Minimap Minimap; /// /// Information about Roshan. (SPECTATOR ONLY)
/// Enabled by including "roshan" "1" in the game state cfg file. ///
- public Roshan Roshan - { - get - { - if (roshan == null) - { - roshan = new Roshan(GetJObject("roshan")); - } - - return roshan; - } - } + public readonly Roshan Roshan; /// /// Information about couriers. (SPECTATOR ONLY)
/// Enabled by including "couriers" "1" in the game state cfg file. ///
- public Couriers Couriers - { - get - { - if (couriers == null) - { - couriers = new Couriers(GetJObject("couriers")); - } - - return couriers; - } - } + public readonly Couriers Couriers; /// /// Information about neutral items. (SPECTATOR ONLY)
/// Enabled by including "neutralitems" "1" in the game state cfg file. ///
- public NeutralItems NeutralItems - { - get - { - if (neutral_items == null) - { - neutral_items = new NeutralItems(GetJObject("neutralitems")); - } - - return neutral_items; - } - } + public readonly NeutralItems NeutralItems; /// /// A previous GameState. @@ -323,12 +112,12 @@ public GameState Previously { get { - if (previously == null) + if (_previous_game_state == null) { - previously = new GameState(GetJObject("previously")); + _previous_game_state = new GameState(GetJObject("previously")); } - return previously; + return _previous_game_state; } } @@ -340,12 +129,12 @@ public FullPlayerDetails LocalPlayer { get { - if (local_player_details == null) + if (_local_player_details == null) { - local_player_details = new FullPlayerDetails(this); + _local_player_details = new FullPlayerDetails(this); } - return local_player_details; + return _local_player_details; } } @@ -357,12 +146,12 @@ public FullTeamDetails RadiantTeamDetails { get { - if (radiant_team_details == null) + if (_radiant_team_details == null) { - radiant_team_details = new FullTeamDetails(PlayerTeam.Radiant, this); + _radiant_team_details = new FullTeamDetails(PlayerTeam.Radiant, this); } - return radiant_team_details; + return _radiant_team_details; } } @@ -374,12 +163,12 @@ public FullTeamDetails DireTeamDetails { get { - if (dire_team_details == null) + if (_dire_team_details == null) { - dire_team_details = new FullTeamDetails(PlayerTeam.Dire, this); + _dire_team_details = new FullTeamDetails(PlayerTeam.Dire, this); } - return dire_team_details; + return _dire_team_details; } } @@ -391,12 +180,12 @@ public FullTeamDetails NeutralTeamDetails { get { - if (neutral_team_details == null) + if (_neutral_team_details == null) { - neutral_team_details = new FullTeamDetails(PlayerTeam.Neutrals, this); + _neutral_team_details = new FullTeamDetails(PlayerTeam.Neutrals, this); } - return neutral_team_details; + return _neutral_team_details; } } @@ -425,5 +214,38 @@ public bool IsLocalPlayer return Player.IsValid() && Player.LocalPlayer.IsValid() && (Player.Teams.Count == 0); } } + + private GameState _previous_game_state; + + // Helpers + + private FullPlayerDetails _local_player_details; + private FullTeamDetails _radiant_team_details; + private FullTeamDetails _dire_team_details; + private FullTeamDetails _neutral_team_details; + + /// + /// Creates a GameState instance based on the given json data. + /// + /// The parsed json data. + public GameState(JObject parsed_data = null) : base(parsed_data) + { + Auth = new Auth(GetJObject("auth")); + Provider = new Provider(GetJObject("provider")); + Map = new Map(GetJObject("map")); + Player = new Player(GetJObject("player")); + Hero = new Hero(GetJObject("hero")); + Abilities = new Abilities(GetJObject("abilities")); + Items = new Items(GetJObject("items")); + Events = new Events(GetJArray("events")); + Buildings = new Buildings(GetJObject("buildings")); + League = new League(GetJObject("league")); + Draft = new Draft(GetJObject("draft")); + Wearables = new Wearables(GetJObject("wearables")); + Minimap = new Minimap(GetJObject("minimap")); + Roshan = new Roshan(GetJObject("roshan")); + Couriers = new Couriers(GetJObject("couriers")); + NeutralItems = new NeutralItems(GetJObject("neutralitems")); + } } } diff --git a/Dota2GSI/GameStateHandler.cs b/Dota2GSI/GameStateHandler.cs new file mode 100644 index 0000000..781f1d4 --- /dev/null +++ b/Dota2GSI/GameStateHandler.cs @@ -0,0 +1,142 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class GameStateHandler : EventHandler + { + private GameState _previous_game_state = new GameState(); + + public GameStateHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + } + + public void OnNewGameState(GameState game_state) + { + if (!game_state.IsValid()) + { + // Invalid game state provided, nothing to do here. + return; + } + + if (!_previous_game_state.IsValid() && game_state.Previously.IsValid()) + { + // Update previous game state cache. + _previous_game_state = game_state.Previously; + } + + // Broadcast changes for custom providers. + + if (!_previous_game_state.LocalPlayer.Equals(game_state.LocalPlayer)) + { + dispatcher.Broadcast(new FullPlayerDetailsUpdated(game_state.LocalPlayer, _previous_game_state.LocalPlayer)); + } + + if (!_previous_game_state.RadiantTeamDetails.Equals(game_state.RadiantTeamDetails)) + { + dispatcher.Broadcast(new FullTeamDetailsUpdated(game_state.RadiantTeamDetails, _previous_game_state.RadiantTeamDetails)); + } + + if (!_previous_game_state.DireTeamDetails.Equals(game_state.DireTeamDetails)) + { + dispatcher.Broadcast(new FullTeamDetailsUpdated(game_state.DireTeamDetails, _previous_game_state.DireTeamDetails)); + } + + if (!_previous_game_state.NeutralTeamDetails.Equals(game_state.NeutralTeamDetails)) + { + dispatcher.Broadcast(new FullTeamDetailsUpdated(game_state.NeutralTeamDetails, _previous_game_state.NeutralTeamDetails)); + } + + // Broadcast changes for providers. + + if (!_previous_game_state.Auth.Equals(game_state.Auth)) + { + dispatcher.Broadcast(new AuthUpdated(game_state.Auth, _previous_game_state.Auth)); + } + + if (!_previous_game_state.Provider.Equals(game_state.Provider)) + { + dispatcher.Broadcast(new ProviderUpdated(game_state.Provider, _previous_game_state.Provider)); + } + + if (!_previous_game_state.Map.Equals(game_state.Map)) + { + dispatcher.Broadcast(new MapUpdated(game_state.Map, _previous_game_state.Map)); + } + + if (!_previous_game_state.Player.Equals(game_state.Player)) + { + // Depends on FullPlayerDetailsUpdated. This broadcast must happen after FullPlayerDetailsUpdated. + dispatcher.Broadcast(new PlayerUpdated(game_state.Player, _previous_game_state.Player)); + } + + if (!_previous_game_state.Hero.Equals(game_state.Hero)) + { + // Depends on FullPlayerDetailsUpdated. This broadcast must happen after FullPlayerDetailsUpdated. + dispatcher.Broadcast(new HeroUpdated(game_state.Hero, _previous_game_state.Hero)); + } + + if (!_previous_game_state.Abilities.Equals(game_state.Abilities)) + { + // Depends on FullPlayerDetailsUpdated. This broadcast must happen after FullPlayerDetailsUpdated. + dispatcher.Broadcast(new AbilitiesUpdated(game_state.Abilities, _previous_game_state.Abilities)); + } + + if (!_previous_game_state.Items.Equals(game_state.Items)) + { + // Depends on FullPlayerDetailsUpdated. This broadcast must happen after FullPlayerDetailsUpdated. + dispatcher.Broadcast(new ItemsUpdated(game_state.Items, _previous_game_state.Items)); + } + + if (!_previous_game_state.Events.Equals(game_state.Events)) + { + // Depends on FullPlayerDetailsUpdated. This broadcast must happen after FullPlayerDetailsUpdated. + dispatcher.Broadcast(new EventsUpdated(game_state.Events, _previous_game_state.Events)); + } + + if (!_previous_game_state.Buildings.Equals(game_state.Buildings)) + { + dispatcher.Broadcast(new BuildingsUpdated(game_state.Buildings, _previous_game_state.Buildings)); + } + + if (!_previous_game_state.League.Equals(game_state.League)) + { + dispatcher.Broadcast(new LeagueUpdated(game_state.League, _previous_game_state.League)); + } + + if (!_previous_game_state.Draft.Equals(game_state.Draft)) + { + dispatcher.Broadcast(new DraftUpdated(game_state.Draft, _previous_game_state.Draft)); + } + + if (!_previous_game_state.Wearables.Equals(game_state.Wearables)) + { + // Depends on FullPlayerDetailsUpdated. This broadcast must happen after FullPlayerDetailsUpdated. + dispatcher.Broadcast(new WearablesUpdated(game_state.Wearables, _previous_game_state.Wearables)); + } + + if (!_previous_game_state.Minimap.Equals(game_state.Minimap)) + { + dispatcher.Broadcast(new MinimapUpdated(game_state.Minimap, _previous_game_state.Minimap)); + } + + if (!_previous_game_state.Roshan.Equals(game_state.Roshan)) + { + dispatcher.Broadcast(new RoshanUpdated(game_state.Roshan, _previous_game_state.Roshan)); + } + + if (!_previous_game_state.Couriers.Equals(game_state.Couriers)) + { + // Depends on FullPlayerDetailsUpdated. This broadcast must happen after FullPlayerDetailsUpdated. + dispatcher.Broadcast(new CouriersUpdated(game_state.Couriers, _previous_game_state.Couriers)); + } + + if (!_previous_game_state.NeutralItems.Equals(game_state.NeutralItems)) + { + dispatcher.Broadcast(new NeutralItemsUpdated(game_state.NeutralItems, _previous_game_state.NeutralItems)); + } + + // Finally update the previous game state cache. + _previous_game_state = game_state; + } + } +} diff --git a/Dota2GSI/GameStateListener.cs b/Dota2GSI/GameStateListener.cs index 677d15c..cb4d951 100644 --- a/Dota2GSI/GameStateListener.cs +++ b/Dota2GSI/GameStateListener.cs @@ -1,6 +1,6 @@ -using Newtonsoft.Json.Linq; +using Dota2GSI.EventMessages; +using Newtonsoft.Json.Linq; using System; -using System.ComponentModel; using System.IO; using System.Net; using System.Text.RegularExpressions; @@ -15,23 +15,16 @@ namespace Dota2GSI public delegate void NewGameStateHandler(GameState gamestate); /// - /// Delegate for handing map game state changes. + /// Delegate for handing game events. /// - /// The new map game state. - public delegate void ChangedMapState(Nodes.DOTA_GameState newGameState); + /// The new game event. + public delegate void GameEventHandler(DotaGameEvent game_event); /// /// /// - public class GameStateListener : IDisposable + public class GameStateListener : Dota2EventsInterface, IDisposable { - private bool isRunning = false; - private int connection_port; - private HttpListener net_Listener; - private AutoResetEvent waitForConnection = new AutoResetEvent(false); - private GameState previousGameState = new GameState(); - private GameState currentGameState = new GameState(); - /// /// The previous game state. /// @@ -39,7 +32,7 @@ public GameState PreviousGameState { get { - return previousGameState; + return _previous_game_state; } } @@ -50,52 +43,89 @@ public GameState CurrentGameState { get { - return currentGameState; + return _current_game_state; } private set { - previousGameState = currentGameState; - currentGameState = value; - RaiseOnNewGameState(); + _previous_game_state = _current_game_state; + _current_game_state = value; + RaiseOnNewGameState(ref _current_game_state); } } /// /// Gets the port that is being listened. /// - public int Port { get { return connection_port; } } + public int Port { get { return _port; } } /// /// Returns whether or not the listener is running. /// - public bool Running { get { return isRunning; } } + public bool Running { get { return _is_running; } } /// /// Event for handing a newly received game state. /// public event NewGameStateHandler NewGameState = delegate { }; + private bool _is_running = false; + private int _port; + private HttpListener _http_listener; + private AutoResetEvent _wait_for_connection = new AutoResetEvent(false); + private GameState _previous_game_state = new GameState(); + private GameState _current_game_state = new GameState(); + + // Dispatcher for game events. + private static EventDispatcher _dispatcher = new EventDispatcher(); + + // Game State handlers. + private AbilitiesHandler _abilities_handler = new AbilitiesHandler(ref _dispatcher); + private AuthHandler _auth_handler = new AuthHandler(ref _dispatcher); + private BuildingsHandler _buildings_handler = new BuildingsHandler(ref _dispatcher); + private CouriersHandler _couriers_handler = new CouriersHandler(ref _dispatcher); + private DraftHandler _draft_handler = new DraftHandler(ref _dispatcher); + private GameplayEventsHandler _gameplay_events_handler = new GameplayEventsHandler(ref _dispatcher); + private HeroHandler _hero_handler = new HeroHandler(ref _dispatcher); + private ItemsHandler _items_handler = new ItemsHandler(ref _dispatcher); + private LeagueHandler _league_handler = new LeagueHandler(ref _dispatcher); + private MapHandler _map_handler = new MapHandler(ref _dispatcher); + private MinimapHandler _minimap_handler = new MinimapHandler(ref _dispatcher); + private NeutralItemsHandler _neutral_items_handler = new NeutralItemsHandler(ref _dispatcher); + private PlayerHandler _player_handler = new PlayerHandler(ref _dispatcher); + private ProviderHandler _provider_handler = new ProviderHandler(ref _dispatcher); + private RoshanHandler _roshan_handler = new RoshanHandler(ref _dispatcher); + private WearablesHandler _wearables_handler = new WearablesHandler(ref _dispatcher); + + // Custom handlers. + private FullDetailsHandler _full_details_handler = new FullDetailsHandler(ref _dispatcher); + + // Overall GameState handler. + private GameStateHandler _game_state_handler = new GameStateHandler(ref _dispatcher); + /// - /// Event for when the map's gamestate changes. + /// Default constructor. /// - public event ChangedMapState ChangedMapState = delegate { }; + private GameStateListener() + { + _dispatcher.GameEvent += OnNewGameEvent; + } /// /// A GameStateListener that listens for connections on http://localhost:port/. /// /// The port to listen on. - public GameStateListener(int Port) + public GameStateListener(int Port) : this() { - connection_port = Port; - net_Listener = new HttpListener(); - net_Listener.Prefixes.Add("http://localhost:" + Port + "/"); + _port = Port; + _http_listener = new HttpListener(); + _http_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) + public GameStateListener(string URI) : this() { if (!URI.EndsWith("/")) { @@ -110,10 +140,10 @@ public GameStateListener(string URI) throw new ArgumentException("Not a valid URI: " + URI); } - connection_port = Convert.ToInt32(PortMatch.Groups[1].Value); + _port = Convert.ToInt32(PortMatch.Groups[1].Value); - net_Listener = new HttpListener(); - net_Listener.Prefixes.Add(URI); + _http_listener = new HttpListener(); + _http_listener.Prefixes.Add(URI); } /// @@ -121,18 +151,18 @@ public GameStateListener(string URI) /// public bool Start() { - if (!isRunning) + if (!_is_running) { Thread ListenerThread = new Thread(new ThreadStart(Run)); try { - net_Listener.Start(); + _http_listener.Start(); } catch (HttpListenerException) { return false; } - isRunning = true; + _is_running = true; // Set this to true, so when the program wants to terminate, // this thread won't stop the program from exiting. @@ -150,29 +180,29 @@ public bool Start() /// public void Stop() { - isRunning = false; + _is_running = false; } private void Run() { - while (isRunning) + while (_is_running) { - net_Listener.BeginGetContext(ReceiveGameState, net_Listener); - waitForConnection.WaitOne(); - waitForConnection.Reset(); + _http_listener.BeginGetContext(ReceiveGameState, _http_listener); + _wait_for_connection.WaitOne(); + _wait_for_connection.Reset(); } - net_Listener.Stop(); + _http_listener.Stop(); } private void ReceiveGameState(IAsyncResult result) { try { - HttpListenerContext context = net_Listener.EndGetContext(result); + HttpListenerContext context = _http_listener.EndGetContext(result); HttpListenerRequest request = context.Request; string json_data; - waitForConnection.Set(); + _wait_for_connection.Set(); using (Stream inputStream = request.InputStream) { @@ -196,34 +226,11 @@ private void ReceiveGameState(IAsyncResult result) } } - private void RaiseOnNewGameState() + private void RaiseOnNewGameState(ref GameState game_state) { - foreach (Delegate d in NewGameState.GetInvocationList()) - { - if (d.Target is ISynchronizeInvoke) - { - (d.Target as ISynchronizeInvoke).BeginInvoke(d, new object[] { CurrentGameState }); - } - else - { - d.DynamicInvoke(CurrentGameState); - } - } + RaiseEvent(NewGameState, game_state); - if (CurrentGameState.Map.GameState != PreviousGameState.Map.GameState) - { - foreach (Delegate d in ChangedMapState.GetInvocationList()) - { - if (d.Target is ISynchronizeInvoke) - { - (d.Target as ISynchronizeInvoke).BeginInvoke(d, new object[] { CurrentGameState.Map.GameState }); - } - else - { - d.DynamicInvoke(CurrentGameState.Map.GameState); - } - } - } + _game_state_handler.OnNewGameState(CurrentGameState); } /// @@ -232,8 +239,8 @@ private void RaiseOnNewGameState() public void Dispose() { Stop(); - waitForConnection.Dispose(); - net_Listener.Close(); + _wait_for_connection.Dispose(); + _http_listener.Close(); } } } diff --git a/Dota2GSI/Nodes/Abilities.cs b/Dota2GSI/Nodes/Abilities.cs index a9c5246..a7088d2 100644 --- a/Dota2GSI/Nodes/Abilities.cs +++ b/Dota2GSI/Nodes/Abilities.cs @@ -19,7 +19,7 @@ public class Abilities : Node /// /// The team players ability details. (SPECTATOR ONLY) /// - public readonly Dictionary> Teams = new Dictionary>(); + public readonly NodeMap> Teams = new NodeMap>(); private Regex _team_id_regex = new Regex(@"team(\d+)"); private Regex _player_id_regex = new Regex(@"player(\d+)"); @@ -36,7 +36,7 @@ internal Abilities(JObject parsed_data = null) : base(parsed_data) if (!Teams.ContainsKey(team_id)) { - Teams.Add(team_id, new Dictionary()); + Teams.Add(team_id, new NodeMap()); } GetMatchingObjects(obj, _player_id_regex, (Match sub_match, JObject sub_obj) => @@ -106,5 +106,27 @@ public override bool IsValid() { return LocalPlayer.IsValid() || base.IsValid(); } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Abilities other && + LocalPlayer.Equals(other.LocalPlayer) && + Teams.Equals(other.Teams); + } + + /// + public override int GetHashCode() + { + int hashCode = 145729690; + hashCode = hashCode * -516479333 + LocalPlayer.GetHashCode(); + hashCode = hashCode * -516479333 + Teams.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/AbilitiesProvider/Ability.cs b/Dota2GSI/Nodes/AbilitiesProvider/Ability.cs index dac064a..93fa7d1 100644 --- a/Dota2GSI/Nodes/AbilitiesProvider/Ability.cs +++ b/Dota2GSI/Nodes/AbilitiesProvider/Ability.cs @@ -87,5 +87,43 @@ public override string ToString() $"ChargeCooldown: {ChargeCooldown}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Ability other && + Name.Equals(other.Name) && + Level.Equals(other.Level) && + CanCast.Equals(other.CanCast) && + IsPassive.Equals(other.IsPassive) && + IsActive.Equals(other.IsActive) && + Cooldown.Equals(other.Cooldown) && + IsUltimate.Equals(other.IsUltimate) && + Charges.Equals(other.Charges) && + MaxCharges.Equals(other.MaxCharges) && + ChargeCooldown.Equals(other.ChargeCooldown); + } + + /// + public override int GetHashCode() + { + int hashCode = 634642291; + hashCode = hashCode * -84013849 + Name.GetHashCode(); + hashCode = hashCode * -84013849 + Level.GetHashCode(); + hashCode = hashCode * -84013849 + CanCast.GetHashCode(); + hashCode = hashCode * -84013849 + IsPassive.GetHashCode(); + hashCode = hashCode * -84013849 + IsActive.GetHashCode(); + hashCode = hashCode * -84013849 + Cooldown.GetHashCode(); + hashCode = hashCode * -84013849 + IsUltimate.GetHashCode(); + hashCode = hashCode * -84013849 + Charges.GetHashCode(); + hashCode = hashCode * -84013849 + MaxCharges.GetHashCode(); + hashCode = hashCode * -84013849 + ChargeCooldown.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/AbilitiesProvider/AbilityDetails.cs b/Dota2GSI/Nodes/AbilitiesProvider/AbilityDetails.cs index 9f2cf1c..a6d886c 100644 --- a/Dota2GSI/Nodes/AbilitiesProvider/AbilityDetails.cs +++ b/Dota2GSI/Nodes/AbilitiesProvider/AbilityDetails.cs @@ -10,7 +10,7 @@ namespace Dota2GSI.Nodes.AbilitiesProvider /// public class AbilityDetails : Node, IEnumerable { - private List _abilities = new List(); + private NodeList _abilities = new NodeList(); /// /// The number of abilities. @@ -65,5 +65,25 @@ public override string ToString() $"Abilities: {_abilities}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is AbilityDetails other && + _abilities.Equals(other._abilities); + } + + /// + public override int GetHashCode() + { + int hashCode = 356743140; + hashCode = hashCode * -394407688 + _abilities.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Auth.cs b/Dota2GSI/Nodes/Auth.cs index df07184..83e8379 100644 --- a/Dota2GSI/Nodes/Auth.cs +++ b/Dota2GSI/Nodes/Auth.cs @@ -24,5 +24,25 @@ public override string ToString() $"Token: {Token}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Auth other && + Token.Equals(other.Token); + } + + /// + public override int GetHashCode() + { + int hashCode = 930327133; + hashCode = hashCode * -487188821 + Token.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Buildings.cs b/Dota2GSI/Nodes/Buildings.cs index 44c1551..f5d051d 100644 --- a/Dota2GSI/Nodes/Buildings.cs +++ b/Dota2GSI/Nodes/Buildings.cs @@ -1,6 +1,5 @@ using Dota2GSI.Nodes.BuildingsProvider; using Newtonsoft.Json.Linq; -using System.Collections.Generic; namespace Dota2GSI.Nodes { @@ -12,7 +11,7 @@ public class Buildings : Node /// /// Gets all buildings layouts. /// - public readonly Dictionary AllBuildings = new Dictionary(); + public readonly NodeMap AllBuildings = new NodeMap(); /// /// Gets Radiant buildings layout. @@ -69,5 +68,25 @@ public override string ToString() $"AllBuildings: {AllBuildings}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Buildings other && + AllBuildings.Equals(other.AllBuildings); + } + + /// + public override int GetHashCode() + { + int hashCode = 107254384; + hashCode = hashCode * -739411027 + AllBuildings.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/BuildingsProvider/Building.cs b/Dota2GSI/Nodes/BuildingsProvider/Building.cs index 5b87baf..344abaa 100644 --- a/Dota2GSI/Nodes/BuildingsProvider/Building.cs +++ b/Dota2GSI/Nodes/BuildingsProvider/Building.cs @@ -30,6 +30,28 @@ public override string ToString() $"Health: {Health}, " + $"MaxHealth: {MaxHealth}" + $"]"; + } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Building other && + Health.Equals(other.Health) && + MaxHealth.Equals(other.MaxHealth); + } + + /// + public override int GetHashCode() + { + int hashCode = 815636796; + hashCode = hashCode * -192822430 + Health.GetHashCode(); + hashCode = hashCode * -192822430 + MaxHealth.GetHashCode(); + return hashCode; } } } diff --git a/Dota2GSI/Nodes/BuildingsProvider/BuildingLayout.cs b/Dota2GSI/Nodes/BuildingsProvider/BuildingLayout.cs index 517ebd4..3be87fe 100644 --- a/Dota2GSI/Nodes/BuildingsProvider/BuildingLayout.cs +++ b/Dota2GSI/Nodes/BuildingsProvider/BuildingLayout.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes.BuildingsProvider @@ -34,32 +33,32 @@ public class BuildingLayout /// /// Top towers. /// - public readonly Dictionary TopTowers = new Dictionary(); + public readonly NodeMap TopTowers = new NodeMap(); /// /// Middle towers. /// - public readonly Dictionary MiddleTowers = new Dictionary(); + public readonly NodeMap MiddleTowers = new NodeMap(); /// /// Bottom towers. /// - public readonly Dictionary BottomTowers = new Dictionary(); + public readonly NodeMap BottomTowers = new NodeMap(); /// /// Top racks. /// - public readonly Dictionary TopRacks = new Dictionary(); + public readonly NodeMap TopRacks = new NodeMap(); /// /// Middle racks. /// - public readonly Dictionary MiddleRacks = new Dictionary(); + public readonly NodeMap MiddleRacks = new NodeMap(); /// /// Bottom racks. /// - public readonly Dictionary BottomRacks = new Dictionary(); + public readonly NodeMap BottomRacks = new NodeMap(); /// /// Ancient. @@ -69,7 +68,7 @@ public class BuildingLayout /// /// Other buildings. /// - public readonly Dictionary OtherBuildings = new Dictionary(); + public readonly NodeMap OtherBuildings = new NodeMap(); private Regex _tower_regex = new Regex(@"tower(\d+)_(top|mid|bot)"); private Regex _racks_regex = new Regex(@"rax_(melee|range)_(top|mid|bot)"); @@ -162,5 +161,39 @@ public override string ToString() $"OtherBuildings: {OtherBuildings}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is BuildingLayout other && + TopTowers.Equals(other.TopTowers) && + MiddleTowers.Equals(other.MiddleTowers) && + BottomTowers.Equals(other.BottomTowers) && + TopRacks.Equals(other.TopRacks) && + MiddleRacks.Equals(other.MiddleRacks) && + BottomRacks.Equals(other.BottomRacks) && + Ancient.Equals(other.Ancient) && + OtherBuildings.Equals(other.OtherBuildings); + } + + /// + public override int GetHashCode() + { + int hashCode = 946659162; + hashCode = hashCode * -439837856 + TopTowers.GetHashCode(); + hashCode = hashCode * -439837856 + MiddleTowers.GetHashCode(); + hashCode = hashCode * -439837856 + BottomTowers.GetHashCode(); + hashCode = hashCode * -439837856 + TopRacks.GetHashCode(); + hashCode = hashCode * -439837856 + MiddleRacks.GetHashCode(); + hashCode = hashCode * -439837856 + BottomRacks.GetHashCode(); + hashCode = hashCode * -439837856 + Ancient.GetHashCode(); + hashCode = hashCode * -439837856 + OtherBuildings.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Couriers.cs b/Dota2GSI/Nodes/Couriers.cs index 2ddf683..287def3 100644 --- a/Dota2GSI/Nodes/Couriers.cs +++ b/Dota2GSI/Nodes/Couriers.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.CouriersProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -14,7 +13,7 @@ public class Couriers : Node /// /// A dictionary mapping of courier ID to courier. /// - public readonly Dictionary CouriersMap = new Dictionary(); + public readonly NodeMap CouriersMap = new NodeMap(); private Regex _courier_regex = new Regex(@"courier(\d+)"); @@ -54,5 +53,25 @@ public override string ToString() $"CouriersMap: {CouriersMap}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Couriers other && + CouriersMap.Equals(other.CouriersMap); + } + + /// + public override int GetHashCode() + { + int hashCode = 743690963; + hashCode = hashCode * -787011489 + CouriersMap.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/CouriersProvider/Courier.cs b/Dota2GSI/Nodes/CouriersProvider/Courier.cs index 8038ee6..b5741c5 100644 --- a/Dota2GSI/Nodes/CouriersProvider/Courier.cs +++ b/Dota2GSI/Nodes/CouriersProvider/Courier.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.Helpers; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes.CouriersProvider @@ -64,7 +63,7 @@ public class Courier : Node /// /// Items the courier is carrying. /// - public readonly Dictionary Items = new Dictionary(); + public readonly NodeMap Items = new NodeMap(); private Regex _item_regex = new Regex(@"item(\d+)"); @@ -90,6 +89,68 @@ internal Courier(JObject parsed_data = null) : base(parsed_data) }); } + /// + /// Gets the inventory item at the specified index. + /// + /// The index. + /// The inventory item. + public CourierItem GetItemAt(int index) + { + if (index < 0 || index > Items.Count - 1) + { + return new CourierItem(); + } + + return Items[index]; + } + + /// + /// Gets the inventory item by item name. + /// + /// The item name to look for. + /// The inventory item. + public CourierItem GetInventoryItem(string item_name) + { + foreach (var item in Items) + { + if (item.Value.Name.Equals(item_name)) + { + return item.Value; + } + } + + return new CourierItem(); + } + + /// + /// Checks if item exists in the inventory. + /// + /// The item name. + /// True if item is in the inventory, false otherwise. + public bool InventoryContains(string item_name) + { + var found_index = InventoryIndexOf(item_name); + return found_index > -1; + } + + /// + /// Gets index of the first occurence of the item in the inventory. + /// + /// The item name. + /// The first index at which item is found, -1 if not found. + public int InventoryIndexOf(string item_name) + { + foreach (var item_kvp in Items) + { + if (item_kvp.Value.Name == item_name) + { + return item_kvp.Key; + } + } + + return -1; + } + /// public override string ToString() { @@ -107,5 +168,45 @@ public override string ToString() $"Items: {Items}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Courier other && + Health.Equals(other.Health) && + MaxHealth.Equals(other.MaxHealth) && + IsAlive.Equals(other.IsAlive) && + RemainingRespawnTime.Equals(other.RemainingRespawnTime) && + Location.Equals(other.Location) && + Rotation.Equals(other.Rotation) && + OwnerID.Equals(other.OwnerID) && + HasFlyingUpgrade.Equals(other.HasFlyingUpgrade) && + IsShielded.Equals(other.IsShielded) && + IsBoosted.Equals(other.IsBoosted) && + Items.Equals(other.Items); + } + + /// + public override int GetHashCode() + { + int hashCode = 232969005; + hashCode = hashCode * -115989773 + Health.GetHashCode(); + hashCode = hashCode * -115989773 + MaxHealth.GetHashCode(); + hashCode = hashCode * -115989773 + IsAlive.GetHashCode(); + hashCode = hashCode * -115989773 + RemainingRespawnTime.GetHashCode(); + hashCode = hashCode * -115989773 + Location.GetHashCode(); + hashCode = hashCode * -115989773 + Rotation.GetHashCode(); + hashCode = hashCode * -115989773 + OwnerID.GetHashCode(); + hashCode = hashCode * -115989773 + HasFlyingUpgrade.GetHashCode(); + hashCode = hashCode * -115989773 + IsShielded.GetHashCode(); + hashCode = hashCode * -115989773 + IsBoosted.GetHashCode(); + hashCode = hashCode * -115989773 + Items.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/CouriersProvider/CourierItem.cs b/Dota2GSI/Nodes/CouriersProvider/CourierItem.cs index 69675dd..58105da 100644 --- a/Dota2GSI/Nodes/CouriersProvider/CourierItem.cs +++ b/Dota2GSI/Nodes/CouriersProvider/CourierItem.cs @@ -31,5 +31,27 @@ public override string ToString() $"OwnerID: {OwnerID}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is CourierItem other && + Name.Equals(other.Name) && + OwnerID.Equals(other.OwnerID); + } + + /// + public override int GetHashCode() + { + int hashCode = 710433606; + hashCode = hashCode * -324247450 + Name.GetHashCode(); + hashCode = hashCode * -324247450 + OwnerID.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Draft.cs b/Dota2GSI/Nodes/Draft.cs index 7f9a8f4..56b1ce5 100644 --- a/Dota2GSI/Nodes/Draft.cs +++ b/Dota2GSI/Nodes/Draft.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.DraftProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -39,7 +38,7 @@ public class Draft : Node /// /// The team draft information. /// - public readonly Dictionary Teams = new Dictionary(); + public readonly NodeMap Teams = new NodeMap(); private Regex _team_id_regex = new Regex(@"team(\d+)"); @@ -86,5 +85,35 @@ public override string ToString() $"Teams: {Teams}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Draft other && + ActiveTeam.Equals(other.ActiveTeam) && + Pick.Equals(other.Pick) && + ActiveTeamRemainingTime.Equals(other.ActiveTeamRemainingTime) && + RadiantBonusTime.Equals(other.RadiantBonusTime) && + DireBonusTime.Equals(other.DireBonusTime) && + Teams.Equals(other.Teams); + } + + /// + public override int GetHashCode() + { + int hashCode = 370669188; + hashCode = hashCode * -824566422 + ActiveTeam.GetHashCode(); + hashCode = hashCode * -824566422 + Pick.GetHashCode(); + hashCode = hashCode * -824566422 + ActiveTeamRemainingTime.GetHashCode(); + hashCode = hashCode * -824566422 + RadiantBonusTime.GetHashCode(); + hashCode = hashCode * -824566422 + DireBonusTime.GetHashCode(); + hashCode = hashCode * -824566422 + Teams.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/DraftProvider/DraftDetails.cs b/Dota2GSI/Nodes/DraftProvider/DraftDetails.cs index 4f26ddc..945fa58 100644 --- a/Dota2GSI/Nodes/DraftProvider/DraftDetails.cs +++ b/Dota2GSI/Nodes/DraftProvider/DraftDetails.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes.DraftProvider @@ -18,12 +17,12 @@ public class DraftDetails : Node /// /// Pick IDs. /// - public readonly Dictionary PickIDs = new Dictionary(); + public readonly NodeMap PickIDs = new NodeMap(); /// /// Pick Hero IDs. /// - public readonly Dictionary PickHeroIDs = new Dictionary(); + public readonly NodeMap PickHeroIDs = new NodeMap(); private Regex _pick_id_regex = new Regex(@"pick(\d+)_id"); private Regex _pick_class_regex = new Regex(@"pick(\d+)_class"); @@ -56,5 +55,29 @@ public override string ToString() $"PickHeroIDs: {PickHeroIDs}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is DraftDetails other && + IsHomeTeam.Equals(other.IsHomeTeam) && + PickIDs.Equals(other.PickIDs) && + PickHeroIDs.Equals(other.PickHeroIDs); + } + + /// + public override int GetHashCode() + { + int hashCode = 148535770; + hashCode = hashCode * -633163745 + IsHomeTeam.GetHashCode(); + hashCode = hashCode * -633163745 + PickIDs.GetHashCode(); + hashCode = hashCode * -633163745 + PickHeroIDs.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Events.cs b/Dota2GSI/Nodes/Events.cs index 05dc642..cd622b4 100644 --- a/Dota2GSI/Nodes/Events.cs +++ b/Dota2GSI/Nodes/Events.cs @@ -10,7 +10,7 @@ namespace Dota2GSI.Nodes /// public class Events : IEnumerable { - private List _events = new List(); + private NodeList _events = new NodeList(); /// /// The number of events. @@ -23,7 +23,7 @@ internal Events(JArray parsed_data = null) : base() { if (parsed_data.Type == JTokenType.Array) { - foreach(JToken element in parsed_data.Children()) + foreach (JToken element in parsed_data.Children()) { if (element.Type == JTokenType.Object) { @@ -39,9 +39,9 @@ internal Events(JArray parsed_data = null) : base() /// /// The team. /// List of events. - public List GetForTeam(PlayerTeam team) + public NodeList GetForTeam(PlayerTeam team) { - List found_events = new List(); + NodeList found_events = new NodeList(); foreach (var evt in _events) { @@ -59,9 +59,9 @@ public List GetForTeam(PlayerTeam team) /// /// The player id to match. /// List of events. - public List GetForPlayer(int player_id) + public NodeList GetForPlayer(int player_id) { - List found_events = new List(); + NodeList found_events = new NodeList(); foreach (var evt in _events) { @@ -112,5 +112,25 @@ public override string ToString() $"Events: {_events}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Events other && + _events.Equals(other._events); + } + + /// + public override int GetHashCode() + { + int hashCode = 395433322; + hashCode = hashCode * -356790273 + _events.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/EventsProvider/Event.cs b/Dota2GSI/Nodes/EventsProvider/Event.cs index 43a59b5..5b91a5b 100644 --- a/Dota2GSI/Nodes/EventsProvider/Event.cs +++ b/Dota2GSI/Nodes/EventsProvider/Event.cs @@ -151,6 +151,44 @@ public override string ToString() $"BountyValue: {BountyValue}, " + $"TeamGold: {TeamGold}" + $"]"; + } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Event other && + GameTime.Equals(other.GameTime) && + EventType.Equals(other.EventType) && + Team.Equals(other.Team) && + KillerPlayerID.Equals(other.KillerPlayerID) && + PlayerID.Equals(other.PlayerID) && + WasSnatched.Equals(other.WasSnatched) && + TipReceiverPlayerID.Equals(other.TipReceiverPlayerID) && + TipAmount.Equals(other.TipAmount) && + BountyValue.Equals(other.BountyValue) && + TeamGold.Equals(other.TeamGold); + } + + /// + public override int GetHashCode() + { + int hashCode = 973835034; + hashCode = hashCode * -320607063 + GameTime.GetHashCode(); + hashCode = hashCode * -320607063 + EventType.GetHashCode(); + hashCode = hashCode * -320607063 + Team.GetHashCode(); + hashCode = hashCode * -320607063 + KillerPlayerID.GetHashCode(); + hashCode = hashCode * -320607063 + PlayerID.GetHashCode(); + hashCode = hashCode * -320607063 + WasSnatched.GetHashCode(); + hashCode = hashCode * -320607063 + TipReceiverPlayerID.GetHashCode(); + hashCode = hashCode * -320607063 + TipAmount.GetHashCode(); + hashCode = hashCode * -320607063 + BountyValue.GetHashCode(); + hashCode = hashCode * -320607063 + TeamGold.GetHashCode(); + return hashCode; } } } diff --git a/Dota2GSI/Nodes/Helpers/FullPlayerDetails.cs b/Dota2GSI/Nodes/Helpers/FullPlayerDetails.cs index 62b6a71..772cd4c 100644 --- a/Dota2GSI/Nodes/Helpers/FullPlayerDetails.cs +++ b/Dota2GSI/Nodes/Helpers/FullPlayerDetails.cs @@ -5,7 +5,6 @@ using Dota2GSI.Nodes.MinimapProvider; using Dota2GSI.Nodes.PlayerProvider; using Dota2GSI.Nodes.WearablesProvider; -using System.Collections.Generic; namespace Dota2GSI.Nodes.Helpers { @@ -14,6 +13,16 @@ namespace Dota2GSI.Nodes.Helpers /// public class FullPlayerDetails { + /// + /// Player's ID. -1 for local player. + /// + public readonly int PlayerID; + + /// + /// True if player details are for the local player. + /// + public bool IsLocalPlayer => PlayerID.Equals(-1); + /// /// Player's basic details. /// @@ -47,10 +56,11 @@ public class FullPlayerDetails /// /// Player's minimap elements. /// - public readonly Dictionary MinimapElements = new Dictionary(); + public readonly NodeMap MinimapElements = new NodeMap(); internal FullPlayerDetails(int player_id, GameState game_state) { + PlayerID = player_id; Details = game_state.Player.GetForPlayer(player_id); Hero = game_state.Hero.GetForPlayer(player_id); Abilities = game_state.Abilities.GetForPlayer(player_id); @@ -62,6 +72,7 @@ internal FullPlayerDetails(int player_id, GameState game_state) internal FullPlayerDetails(GameState game_state) { + PlayerID = -1; Details = game_state.Player.LocalPlayer; Hero = game_state.Hero.LocalPlayer; Abilities = game_state.Abilities.LocalPlayer; @@ -84,5 +95,37 @@ public override string ToString() $"PlayerMinimapElements: {MinimapElements}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is FullPlayerDetails other && + Details.Equals(other.Details) && + Hero.Equals(other.Hero) && + Abilities.Equals(other.Abilities) && + Items.Equals(other.Items) && + Wearables.Equals(other.Wearables) && + Courier.Equals(other.Courier) && + MinimapElements.Equals(other.MinimapElements); + } + + /// + public override int GetHashCode() + { + int hashCode = 339357341; + hashCode = hashCode * -959873209 + Details.GetHashCode(); + hashCode = hashCode * -959873209 + Hero.GetHashCode(); + hashCode = hashCode * -959873209 + Abilities.GetHashCode(); + hashCode = hashCode * -959873209 + Items.GetHashCode(); + hashCode = hashCode * -959873209 + Wearables.GetHashCode(); + hashCode = hashCode * -959873209 + Courier.GetHashCode(); + hashCode = hashCode * -959873209 + MinimapElements.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Helpers/FullTeamDetails.cs b/Dota2GSI/Nodes/Helpers/FullTeamDetails.cs index b8fad72..3d1b294 100644 --- a/Dota2GSI/Nodes/Helpers/FullTeamDetails.cs +++ b/Dota2GSI/Nodes/Helpers/FullTeamDetails.cs @@ -3,7 +3,6 @@ using Dota2GSI.Nodes.EventsProvider; using Dota2GSI.Nodes.MinimapProvider; using Dota2GSI.Nodes.NeutralItemsProvider; -using System.Collections.Generic; namespace Dota2GSI.Nodes.Helpers { @@ -20,7 +19,7 @@ public class FullTeamDetails /// /// This team's players. /// - public readonly Dictionary Players = new Dictionary(); + public readonly NodeMap Players = new NodeMap(); /// /// This team's draft. @@ -42,12 +41,12 @@ public class FullTeamDetails /// Key is element ID.
/// Value is minimap element. ///
- public readonly Dictionary MinimapElements = new Dictionary(); + public readonly NodeMap MinimapElements = new NodeMap(); /// /// This team's recent events. /// - public readonly List Events = new List(); + public readonly NodeList Events = new NodeList(); /// /// Is this team a winner? @@ -58,7 +57,7 @@ internal FullTeamDetails(PlayerTeam team, GameState game_state) { Team = team; - foreach(var team_player_kvp in game_state.Player.GetForTeam(team)) + foreach (var team_player_kvp in game_state.Player.GetForTeam(team)) { Players.Add(team_player_kvp.Key, new FullPlayerDetails(team_player_kvp.Key, game_state)); } @@ -85,5 +84,39 @@ public override string ToString() $"IsWinner: {IsWinner}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is FullTeamDetails other && + Team.Equals(other.Team) && + Players.Equals(other.Players) && + Draft.Equals(other.Draft) && + NeutralItems.Equals(other.NeutralItems) && + Buildings.Equals(other.Buildings) && + MinimapElements.Equals(other.MinimapElements) && + Events.Equals(other.Events) && + IsWinner.Equals(other.IsWinner); + } + + /// + public override int GetHashCode() + { + int hashCode = 327356736; + hashCode = hashCode * -578827851 + Team.GetHashCode(); + hashCode = hashCode * -578827851 + Players.GetHashCode(); + hashCode = hashCode * -578827851 + Draft.GetHashCode(); + hashCode = hashCode * -578827851 + NeutralItems.GetHashCode(); + hashCode = hashCode * -578827851 + Buildings.GetHashCode(); + hashCode = hashCode * -578827851 + MinimapElements.GetHashCode(); + hashCode = hashCode * -578827851 + Events.GetHashCode(); + hashCode = hashCode * -578827851 + IsWinner.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Helpers/Vector2D.cs b/Dota2GSI/Nodes/Helpers/Vector2D.cs index 1e226c0..de4cc14 100644 --- a/Dota2GSI/Nodes/Helpers/Vector2D.cs +++ b/Dota2GSI/Nodes/Helpers/Vector2D.cs @@ -27,11 +27,7 @@ public Vector2D(int x, int y) Y = y; } - /// - /// Equates this Vector2D object to another object. - /// - /// The other object to compare against. - /// True if the two objects are equal, false otherwise. + /// public override bool Equals(object obj) { return obj is Vector2D other && @@ -39,10 +35,7 @@ public override bool Equals(object obj) Y == other.Y; } - /// - /// Calculates unique hash code for this object. - /// - /// The hash code. + /// public override int GetHashCode() { int hashCode = 1861411795; diff --git a/Dota2GSI/Nodes/Hero.cs b/Dota2GSI/Nodes/Hero.cs index dfcaa4c..fdf2365 100644 --- a/Dota2GSI/Nodes/Hero.cs +++ b/Dota2GSI/Nodes/Hero.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.HeroProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -19,7 +18,7 @@ public class Hero : Node /// /// The team players hero details. (SPECTATOR ONLY) /// - public readonly Dictionary> Teams = new Dictionary>(); + public readonly NodeMap> Teams = new NodeMap>(); private Regex _team_id_regex = new Regex(@"team(\d+)"); private Regex _player_id_regex = new Regex(@"player(\d+)"); @@ -36,7 +35,7 @@ internal Hero(JObject parsed_data = null) : base(parsed_data) if (!Teams.ContainsKey(team_id)) { - Teams.Add(team_id, new Dictionary()); + Teams.Add(team_id, new NodeMap()); } GetMatchingObjects(obj, _player_id_regex, (Match sub_match, JObject sub_obj) => @@ -61,14 +60,14 @@ internal Hero(JObject parsed_data = null) : base(parsed_data) /// /// The team. /// A dictionary of player id mapped to their hero details. - public Dictionary GetForTeam(PlayerTeam team) + public NodeMap GetForTeam(PlayerTeam team) { if (Teams.ContainsKey(team)) { return Teams[team]; } - return new Dictionary(); + return new NodeMap(); } /// @@ -106,5 +105,27 @@ public override bool IsValid() { return LocalPlayer.IsValid() || base.IsValid(); } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Hero other && + LocalPlayer.Equals(other.LocalPlayer) && + Teams.Equals(other.Teams); + } + + /// + public override int GetHashCode() + { + int hashCode = 222282086; + hashCode = hashCode * -422594062 + LocalPlayer.GetHashCode(); + hashCode = hashCode * -422594062 + Teams.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/HeroProvider/HeroDetails.cs b/Dota2GSI/Nodes/HeroProvider/HeroDetails.cs index 931fcf1..142238f 100644 --- a/Dota2GSI/Nodes/HeroProvider/HeroDetails.cs +++ b/Dota2GSI/Nodes/HeroProvider/HeroDetails.cs @@ -1,5 +1,6 @@ -using Dota2GSI.Nodes.Helpers; +using Dota2GSI.Nodes.Helpers; using Newtonsoft.Json.Linq; +using System.Linq; namespace Dota2GSI.Nodes.HeroProvider { @@ -29,6 +30,57 @@ public enum TalentTreeSpec Both } + /// + /// The hero state. + /// + public enum HeroState + { + /// + /// No states. + /// + None = 0, + + /// + /// Hero is silenced. + /// + Silenced, + + /// + /// Hero is stunned. + /// + Stunned, + + /// + /// Hero is disarmed. + /// + Disarmed, + + /// + /// Hero is magic immune. + /// + MagicImmune, + + /// + /// Hero is hexed. + /// + Hexed, + + /// + /// Hero is broken. + /// + Broken, + + /// + /// Hero is smoked. + /// + Smoked, + + /// + /// Hero is debuffed. + /// + Debuffed + } + /// /// Class representing hero details. /// @@ -110,40 +162,55 @@ public class HeroDetails : Node public readonly int ManaPercent; /// - /// A boolean representing whether the hero is silenced. + /// Current hero state. /// - public readonly bool IsSilenced; + public readonly HeroState HeroState; - /// - /// A boolean representing whether the hero is stunned. - /// - public readonly bool IsStunned; + /// + /// A boolean representing whether the hero is silenced. + /// + public bool IsSilenced => HeroState.HasFlag(HeroState.Silenced); - /// - /// A boolean representing whether the hero is disarmed. - /// - public readonly bool IsDisarmed; + /// + /// A boolean representing whether the hero is stunned. + /// + public bool IsStunned => HeroState.HasFlag(HeroState.Stunned); - /// - /// A boolean representing whether the hero is magic immune. - /// - public readonly bool IsMagicImmune; + /// + /// A boolean representing whether the hero is disarmed. + /// + public bool IsDisarmed => HeroState.HasFlag(HeroState.Disarmed); - /// - /// A boolean representing whether the hero is hexed. - /// - public readonly bool IsHexed; + /// + /// A boolean representing whether the hero is magic immune. + /// + public bool IsMagicImmune => HeroState.HasFlag(HeroState.MagicImmune); + + /// + /// A boolean representing whether the hero is hexed. + /// + public bool IsHexed => HeroState.HasFlag(HeroState.Hexed); + + /// + /// A boolean representing whether the hero is broken. + /// + public bool IsBreak => HeroState.HasFlag(HeroState.Broken); + + /// + /// A boolean representing whether the hero is smoked. + /// + public bool IsSmoked => HeroState.HasFlag(HeroState.Smoked); + + /// + /// A boolean representing whether the hero is debuffed. + /// + public bool HasDebuff => HeroState.HasFlag(HeroState.Debuffed); /// /// A boolean representing whether the hero is muted. /// public readonly bool IsMuted; - /// - /// A boolean representing whether the hero is broken. - /// - public readonly bool IsBreak; - /// /// A boolean representing whether the hero has the aghanims scepter upgrade. /// @@ -154,16 +221,6 @@ public class HeroDetails : Node /// public readonly bool HasAghanimsShardUpgrade; - /// - /// A boolean representing whether the hero is smoked. - /// - public readonly bool IsSmoked; - - /// - /// A boolean representing whether the hero is debuffed. - /// - public readonly bool HasDebuff; - /// /// A boolean representing whether this hero is currently selected by the spectator. (SPECTATOR ONLY) /// @@ -196,19 +253,53 @@ internal HeroDetails(JObject parsed_data = null) : base(parsed_data) 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"); HasAghanimsScepterUpgrade = GetBool("aghanims_scepter"); HasAghanimsShardUpgrade = GetBool("aghanims_shard"); - IsSmoked = GetBool("smoked"); - HasDebuff = GetBool("has_debuff"); SelectedUnit = GetBool("selected_unit"); + HeroState = HeroState.None; + + if (GetBool("silenced")) + { + HeroState &= HeroState.Silenced; + } + + if (GetBool("stunned")) + { + HeroState &= HeroState.Stunned; + } + + if (GetBool("disarmed")) + { + HeroState &= HeroState.Disarmed; + } + + if (GetBool("magicimmune")) + { + HeroState &= HeroState.MagicImmune; + } + + if (GetBool("hexed")) + { + HeroState &= HeroState.Hexed; + } + + if (GetBool("break")) + { + HeroState &= HeroState.Broken; + } + + if (GetBool("smoked")) + { + HeroState &= HeroState.Smoked; + } + + if (GetBool("has_debuff")) + { + HeroState &= HeroState.Debuffed; + } + TalentTree = new TalentTreeSpec[4]; for (int i = 0; i < TalentTree.Length; i++) { @@ -259,21 +350,74 @@ public override string ToString() $"Mana: {Mana}, " + $"MaxMana: {MaxMana}, " + $"ManaPercent: {ManaPercent}, " + - $"IsSilenced: {IsSilenced}, " + - $"IsStunned: {IsStunned}, " + - $"IsDisarmed: {IsDisarmed}, " + - $"IsMagicImmune: {IsMagicImmune}, " + - $"IsHexed: {IsHexed}, " + + $"IsSilenced: {HeroState}, " + $"IsMuted: {IsMuted}, " + - $"IsBreak: {IsBreak}, " + $"HasAghanimsScepterUpgrade: {HasAghanimsScepterUpgrade}, " + $"HasAghanimsShardUpgrade: {HasAghanimsShardUpgrade}, " + - $"IsSmoked: {IsSmoked}, " + - $"HasDebuff: {HasDebuff}, " + $"SelectedUnit: {SelectedUnit}, " + $"TalentTree: {TalentTree}, " + $"AttributesLevel: {AttributesLevel}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is HeroDetails other && + Location.Equals(other.Location) && + ID.Equals(other.ID) && + Name.Equals(other.Name) && + Level.Equals(other.Level) && + Experience.Equals(other.Experience) && + IsAlive.Equals(other.IsAlive) && + SecondsToRespawn.Equals(other.SecondsToRespawn) && + BuybackCost.Equals(other.BuybackCost) && + BuybackCooldown.Equals(other.BuybackCooldown) && + Health.Equals(other.Health) && + MaxHealth.Equals(other.MaxHealth) && + HealthPercent.Equals(other.HealthPercent) && + Mana.Equals(other.Mana) && + MaxMana.Equals(other.MaxMana) && + ManaPercent.Equals(other.ManaPercent) && + IsMuted.Equals(other.IsMuted) && + HasAghanimsScepterUpgrade.Equals(other.HasAghanimsScepterUpgrade) && + HasAghanimsShardUpgrade.Equals(other.HasAghanimsShardUpgrade) && + SelectedUnit.Equals(other.SelectedUnit) && + Enumerable.SequenceEqual(TalentTree, other.TalentTree) && + AttributesLevel.Equals(other.AttributesLevel); + } + + /// + public override int GetHashCode() + { + int hashCode = 189436882; + hashCode = hashCode * -287957234 + Location.GetHashCode(); + hashCode = hashCode * -287957234 + ID.GetHashCode(); + hashCode = hashCode * -287957234 + Name.GetHashCode(); + hashCode = hashCode * -287957234 + Level.GetHashCode(); + hashCode = hashCode * -287957234 + Experience.GetHashCode(); + hashCode = hashCode * -287957234 + IsAlive.GetHashCode(); + hashCode = hashCode * -287957234 + SecondsToRespawn.GetHashCode(); + hashCode = hashCode * -287957234 + BuybackCost.GetHashCode(); + hashCode = hashCode * -287957234 + BuybackCooldown.GetHashCode(); + hashCode = hashCode * -287957234 + Health.GetHashCode(); + hashCode = hashCode * -287957234 + MaxHealth.GetHashCode(); + hashCode = hashCode * -287957234 + HealthPercent.GetHashCode(); + hashCode = hashCode * -287957234 + Mana.GetHashCode(); + hashCode = hashCode * -287957234 + MaxMana.GetHashCode(); + hashCode = hashCode * -287957234 + ManaPercent.GetHashCode(); + hashCode = hashCode * -287957234 + IsMuted.GetHashCode(); + hashCode = hashCode * -287957234 + HasAghanimsScepterUpgrade.GetHashCode(); + hashCode = hashCode * -287957234 + HasAghanimsShardUpgrade.GetHashCode(); + hashCode = hashCode * -287957234 + SelectedUnit.GetHashCode(); + hashCode = hashCode * -287957234 + TalentTree.GetHashCode(); + hashCode = hashCode * -287957234 + AttributesLevel.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Items.cs b/Dota2GSI/Nodes/Items.cs index 27ed67b..0509ce0 100644 --- a/Dota2GSI/Nodes/Items.cs +++ b/Dota2GSI/Nodes/Items.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.ItemsProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -19,7 +18,7 @@ public class Items : Node /// /// The team players items. (SPECTATOR ONLY) /// - public readonly Dictionary> Teams = new Dictionary>(); + public readonly NodeMap> Teams = new NodeMap>(); private Regex _team_id_regex = new Regex(@"team(\d+)"); private Regex _player_id_regex = new Regex(@"player(\d+)"); @@ -36,7 +35,7 @@ internal Items(JObject parsed_data = null) : base(parsed_data) if (!Teams.ContainsKey(team_id)) { - Teams.Add(team_id, new Dictionary()); + Teams.Add(team_id, new NodeMap()); } GetMatchingObjects(parsed_data, _player_id_regex, (Match sub_match, JObject sub_obj) => @@ -61,14 +60,14 @@ internal Items(JObject parsed_data = null) : base(parsed_data) /// /// The team. /// A dictionary of player id mapped to their item details. - public Dictionary GetForTeam(PlayerTeam team) + public NodeMap GetForTeam(PlayerTeam team) { if (Teams.ContainsKey(team)) { return Teams[team]; } - return new Dictionary(); + return new NodeMap(); } /// @@ -106,5 +105,27 @@ public override bool IsValid() { return LocalPlayer.IsValid() || base.IsValid(); } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Items other && + LocalPlayer.Equals(other.LocalPlayer) && + Teams.Equals(other.Teams); + } + + /// + public override int GetHashCode() + { + int hashCode = 787251810; + hashCode = hashCode * -635208756 + LocalPlayer.GetHashCode(); + hashCode = hashCode * -635208756 + Teams.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/ItemsProvider/Item.cs b/Dota2GSI/Nodes/ItemsProvider/Item.cs index 90eb332..49f718f 100644 --- a/Dota2GSI/Nodes/ItemsProvider/Item.cs +++ b/Dota2GSI/Nodes/ItemsProvider/Item.cs @@ -167,6 +167,48 @@ public override string ToString() $"ChargeCooldown: {ChargeCooldown}, " + $"Charges: {Charges}" + $"]"; + } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Item other && + Name.Equals(other.Name) && + Purchaser.Equals(other.Purchaser) && + ItemLevel.Equals(other.ItemLevel) && + ContainsRune.Equals(other.ContainsRune) && + CanCast.Equals(other.CanCast) && + Cooldown.Equals(other.Cooldown) && + IsPassive.Equals(other.IsPassive) && + ItemCharges.Equals(other.ItemCharges) && + AbilityCharges.Equals(other.AbilityCharges) && + MaxCharges.Equals(other.MaxCharges) && + ChargeCooldown.Equals(other.ChargeCooldown) && + Charges.Equals(other.Charges); + } + + /// + public override int GetHashCode() + { + int hashCode = 578432725; + hashCode = hashCode * -280156728 + Name.GetHashCode(); + hashCode = hashCode * -280156728 + Purchaser.GetHashCode(); + hashCode = hashCode * -280156728 + ItemLevel.GetHashCode(); + hashCode = hashCode * -280156728 + ContainsRune.GetHashCode(); + hashCode = hashCode * -280156728 + CanCast.GetHashCode(); + hashCode = hashCode * -280156728 + Cooldown.GetHashCode(); + hashCode = hashCode * -280156728 + IsPassive.GetHashCode(); + hashCode = hashCode * -280156728 + ItemCharges.GetHashCode(); + hashCode = hashCode * -280156728 + AbilityCharges.GetHashCode(); + hashCode = hashCode * -280156728 + MaxCharges.GetHashCode(); + hashCode = hashCode * -280156728 + ChargeCooldown.GetHashCode(); + hashCode = hashCode * -280156728 + Charges.GetHashCode(); + return hashCode; } } } diff --git a/Dota2GSI/Nodes/ItemsProvider/ItemDetails.cs b/Dota2GSI/Nodes/ItemsProvider/ItemDetails.cs index 61e1ab4..04853ab 100644 --- a/Dota2GSI/Nodes/ItemsProvider/ItemDetails.cs +++ b/Dota2GSI/Nodes/ItemsProvider/ItemDetails.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json.Linq; -using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -13,12 +12,12 @@ public class ItemDetails : Node /// /// List of the inventory items. /// - public readonly List Inventory = new List(); + public readonly NodeList Inventory = new NodeList(); /// /// List of the stash items. /// - public readonly List Stash = new List(); + public readonly NodeList Stash = new NodeList(); /// /// Number of items in the inventory. @@ -113,12 +112,7 @@ public Item GetInventoryItem(string item_name) public bool InventoryContains(string item_name) { var found_index = InventoryIndexOf(item_name); - if (found_index > -1) - { - return true; - } - - return false; + return found_index > -1; } /// @@ -128,9 +122,9 @@ public bool InventoryContains(string item_name) /// The first index at which item is found, -1 if not found. public int InventoryIndexOf(string item_name) { - for (int x = 0; x < this.Inventory.Count; x++) + for (int x = 0; x < Inventory.Count; x++) { - if (this.Inventory[x].Name == item_name) + if (Inventory[x].Name == item_name) { return x; } @@ -180,12 +174,7 @@ public Item GetStashItem(string item_name) public bool StashContains(string item_name) { var found_index = StashIndexOf(item_name); - if (found_index > -1) - { - return true; - } - - return false; + return found_index > -1; } /// @@ -195,9 +184,9 @@ public bool StashContains(string item_name) /// The first index at which item is found, -1 if not found. public int StashIndexOf(string item_name) { - for (int x = 0; x < this.Stash.Count; x++) + for (int x = 0; x < Stash.Count; x++) { - if (this.Stash[x].Name == item_name) + if (Stash[x].Name == item_name) { return x; } @@ -216,5 +205,31 @@ public override string ToString() $"Neutral: {Neutral}, " + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is ItemDetails other && + Inventory.Equals(other.Inventory) && + Stash.Equals(other.Stash) && + Teleport.Equals(other.Teleport) && + Neutral.Equals(other.Neutral); + } + + /// + public override int GetHashCode() + { + int hashCode = 74222497; + hashCode = hashCode * -709592358 + Inventory.GetHashCode(); + hashCode = hashCode * -709592358 + Stash.GetHashCode(); + hashCode = hashCode * -709592358 + Teleport.GetHashCode(); + hashCode = hashCode * -709592358 + Neutral.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/League.cs b/Dota2GSI/Nodes/League.cs index ed5a019..51e8b43 100644 --- a/Dota2GSI/Nodes/League.cs +++ b/Dota2GSI/Nodes/League.cs @@ -1,6 +1,5 @@ using Dota2GSI.Nodes.LeagueProvider; using Newtonsoft.Json.Linq; -using System.Collections.Generic; namespace Dota2GSI.Nodes { @@ -168,7 +167,7 @@ public class League : Node /// /// The streams for the league. /// - public readonly List Streams = new List(); + public readonly NodeList Streams = new NodeList(); internal League(JObject parsed_data = null) : base(parsed_data) { @@ -245,5 +244,77 @@ public override string ToString() $"Streams: {Streams}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is League other && + SeriesType.Equals(other.SeriesType) && + SelectionPriority.Equals(other.SelectionPriority) && + LeagueID.Equals(other.LeagueID) && + MatchID.Equals(other.MatchID) && + Name.Equals(other.Name) && + Tier.Equals(other.Tier) && + Region.Equals(other.Region) && + Url.Equals(other.Url) && + Description.Equals(other.Description) && + Notes.Equals(other.Notes) && + StartTimestamp.Equals(other.StartTimestamp) && + EndTimestamp.Equals(other.EndTimestamp) && + ProCircuitPoints.Equals(other.ProCircuitPoints) && + ImageBits.Equals(other.ImageBits) && + Status.Equals(other.Status) && + MostRecentActivity.Equals(other.MostRecentActivity) && + RegistrationPeriod.Equals(other.RegistrationPeriod) && + BasePrizePool.Equals(other.BasePrizePool) && + TotalPrizePool.Equals(other.TotalPrizePool) && + LeagueNoteID.Equals(other.LeagueNoteID) && + RadiantTeam.Equals(other.RadiantTeam) && + DireTeam.Equals(other.DireTeam) && + SeriesID.Equals(other.SeriesID) && + StartTime.Equals(other.StartTime) && + FirstTeamID.Equals(other.FirstTeamID) && + SecondTeamID.Equals(other.SecondTeamID) && + Streams.Equals(other.Streams); + } + + /// + public override int GetHashCode() + { + int hashCode = 761863323; + hashCode = hashCode * -43571779 + SeriesType.GetHashCode(); + hashCode = hashCode * -43571779 + SelectionPriority.GetHashCode(); + hashCode = hashCode * -43571779 + LeagueID.GetHashCode(); + hashCode = hashCode * -43571779 + MatchID.GetHashCode(); + hashCode = hashCode * -43571779 + Name.GetHashCode(); + hashCode = hashCode * -43571779 + Tier.GetHashCode(); + hashCode = hashCode * -43571779 + Region.GetHashCode(); + hashCode = hashCode * -43571779 + Url.GetHashCode(); + hashCode = hashCode * -43571779 + Description.GetHashCode(); + hashCode = hashCode * -43571779 + Notes.GetHashCode(); + hashCode = hashCode * -43571779 + StartTimestamp.GetHashCode(); + hashCode = hashCode * -43571779 + EndTimestamp.GetHashCode(); + hashCode = hashCode * -43571779 + ProCircuitPoints.GetHashCode(); + hashCode = hashCode * -43571779 + ImageBits.GetHashCode(); + hashCode = hashCode * -43571779 + Status.GetHashCode(); + hashCode = hashCode * -43571779 + MostRecentActivity.GetHashCode(); + hashCode = hashCode * -43571779 + RegistrationPeriod.GetHashCode(); + hashCode = hashCode * -43571779 + BasePrizePool.GetHashCode(); + hashCode = hashCode * -43571779 + TotalPrizePool.GetHashCode(); + hashCode = hashCode * -43571779 + LeagueNoteID.GetHashCode(); + hashCode = hashCode * -43571779 + RadiantTeam.GetHashCode(); + hashCode = hashCode * -43571779 + DireTeam.GetHashCode(); + hashCode = hashCode * -43571779 + SeriesID.GetHashCode(); + hashCode = hashCode * -43571779 + StartTime.GetHashCode(); + hashCode = hashCode * -43571779 + FirstTeamID.GetHashCode(); + hashCode = hashCode * -43571779 + SecondTeamID.GetHashCode(); + hashCode = hashCode * -43571779 + Streams.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/LeagueProvider/LeagueTeam.cs b/Dota2GSI/Nodes/LeagueProvider/LeagueTeam.cs index 757bdfd..c3e6067 100644 --- a/Dota2GSI/Nodes/LeagueProvider/LeagueTeam.cs +++ b/Dota2GSI/Nodes/LeagueProvider/LeagueTeam.cs @@ -45,5 +45,31 @@ public override string ToString() $"SeriesWins: {SeriesWins}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is LeagueTeam other && + TeamID.Equals(other.TeamID) && + TeamTag.Equals(other.TeamTag) && + TeamName.Equals(other.TeamName) && + SeriesWins.Equals(other.SeriesWins); + } + + /// + public override int GetHashCode() + { + int hashCode = 248757312; + hashCode = hashCode * -665381140 + TeamID.GetHashCode(); + hashCode = hashCode * -665381140 + TeamTag.GetHashCode(); + hashCode = hashCode * -665381140 + TeamName.GetHashCode(); + hashCode = hashCode * -665381140 + SeriesWins.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/LeagueProvider/SelectionPriority.cs b/Dota2GSI/Nodes/LeagueProvider/SelectionPriority.cs index acd2424..ae0ae7f 100644 --- a/Dota2GSI/Nodes/LeagueProvider/SelectionPriority.cs +++ b/Dota2GSI/Nodes/LeagueProvider/SelectionPriority.cs @@ -126,5 +126,35 @@ public override string ToString() $"UsedCoinToss: {UsedCoinToss}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is SelectionPriority other && + Rules.Equals(other.Rules) && + PreviousPriorityTeamID.Equals(other.PreviousPriorityTeamID) && + CurrentPriorityTeamID.Equals(other.CurrentPriorityTeamID) && + PriorityTeamChoice.Equals(other.PriorityTeamChoice) && + NonPriorityTeamChoice.Equals(other.NonPriorityTeamChoice) && + UsedCoinToss.Equals(other.UsedCoinToss); + } + + /// + public override int GetHashCode() + { + int hashCode = 107532357; + hashCode = hashCode * -96977732 + Rules.GetHashCode(); + hashCode = hashCode * -96977732 + PreviousPriorityTeamID.GetHashCode(); + hashCode = hashCode * -96977732 + CurrentPriorityTeamID.GetHashCode(); + hashCode = hashCode * -96977732 + PriorityTeamChoice.GetHashCode(); + hashCode = hashCode * -96977732 + NonPriorityTeamChoice.GetHashCode(); + hashCode = hashCode * -96977732 + UsedCoinToss.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/LeagueProvider/Stream.cs b/Dota2GSI/Nodes/LeagueProvider/Stream.cs index aba4583..ce0d81c 100644 --- a/Dota2GSI/Nodes/LeagueProvider/Stream.cs +++ b/Dota2GSI/Nodes/LeagueProvider/Stream.cs @@ -59,5 +59,35 @@ public override string ToString() $"VodURL: {VodURL}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Stream other && + StreamID.Equals(other.StreamID) && + Language.Equals(other.Language) && + Name.Equals(other.Name) && + BroadcastProvider.Equals(other.BroadcastProvider) && + StreamURL.Equals(other.StreamURL) && + VodURL.Equals(other.VodURL); + } + + /// + public override int GetHashCode() + { + int hashCode = 192157661; + hashCode = hashCode * -913935863 + StreamID.GetHashCode(); + hashCode = hashCode * -913935863 + Language.GetHashCode(); + hashCode = hashCode * -913935863 + Name.GetHashCode(); + hashCode = hashCode * -913935863 + BroadcastProvider.GetHashCode(); + hashCode = hashCode * -913935863 + StreamURL.GetHashCode(); + hashCode = hashCode * -913935863 + VodURL.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Map.cs b/Dota2GSI/Nodes/Map.cs index a09d186..66ddd8d 100644 --- a/Dota2GSI/Nodes/Map.cs +++ b/Dota2GSI/Nodes/Map.cs @@ -275,6 +275,58 @@ public override string ToString() $"RoshanState: {RoshanState}, " + $"RoshanStateEndTime: {RoshanStateEndTime}" + $"]"; + } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Map other && + Name.Equals(other.Name) && + MatchID.Equals(other.MatchID) && + GameTime.Equals(other.GameTime) && + ClockTime.Equals(other.ClockTime) && + IsDaytime.Equals(other.IsDaytime) && + IsNightstalkerNight.Equals(other.IsNightstalkerNight) && + RadiantScore.Equals(other.RadiantScore) && + DireScore.Equals(other.DireScore) && + GameState.Equals(other.GameState) && + IsPaused.Equals(other.IsPaused) && + WinningTeam.Equals(other.WinningTeam) && + CustomGameName.Equals(other.CustomGameName) && + WardPurchaseCooldown.Equals(other.WardPurchaseCooldown) && + RadiantWardPurchaseCooldown.Equals(other.RadiantWardPurchaseCooldown) && + DireWardPurchaseCooldown.Equals(other.DireWardPurchaseCooldown) && + RoshanState.Equals(other.RoshanState) && + RoshanStateEndTime.Equals(other.RoshanStateEndTime); + } + + /// + public override int GetHashCode() + { + int hashCode = 225797103; + hashCode = hashCode * -955669127 + Name.GetHashCode(); + hashCode = hashCode * -955669127 + MatchID.GetHashCode(); + hashCode = hashCode * -955669127 + GameTime.GetHashCode(); + hashCode = hashCode * -955669127 + ClockTime.GetHashCode(); + hashCode = hashCode * -955669127 + IsDaytime.GetHashCode(); + hashCode = hashCode * -955669127 + IsNightstalkerNight.GetHashCode(); + hashCode = hashCode * -955669127 + RadiantScore.GetHashCode(); + hashCode = hashCode * -955669127 + DireScore.GetHashCode(); + hashCode = hashCode * -955669127 + GameState.GetHashCode(); + hashCode = hashCode * -955669127 + IsPaused.GetHashCode(); + hashCode = hashCode * -955669127 + WinningTeam.GetHashCode(); + hashCode = hashCode * -955669127 + CustomGameName.GetHashCode(); + hashCode = hashCode * -955669127 + WardPurchaseCooldown.GetHashCode(); + hashCode = hashCode * -955669127 + RadiantWardPurchaseCooldown.GetHashCode(); + hashCode = hashCode * -955669127 + DireWardPurchaseCooldown.GetHashCode(); + hashCode = hashCode * -955669127 + RoshanState.GetHashCode(); + hashCode = hashCode * -955669127 + RoshanStateEndTime.GetHashCode(); + return hashCode; } } } diff --git a/Dota2GSI/Nodes/Minimap.cs b/Dota2GSI/Nodes/Minimap.cs index ac022bc..905619b 100644 --- a/Dota2GSI/Nodes/Minimap.cs +++ b/Dota2GSI/Nodes/Minimap.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.MinimapProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -16,7 +15,7 @@ public class Minimap : Node /// Key is element ID.
/// Value is minimap element. ///
- public readonly Dictionary Elements = new Dictionary(); + public readonly NodeMap Elements = new NodeMap(); private Regex _object_regex = new Regex(@"o(\d+)"); internal Minimap(JObject parsed_data = null) : base(parsed_data) @@ -36,9 +35,9 @@ internal Minimap(JObject parsed_data = null) : base(parsed_data) ///
/// The team. /// The minimap elements. - public Dictionary GetForTeam(PlayerTeam team) + public NodeMap GetForTeam(PlayerTeam team) { - Dictionary found_elements = new Dictionary(); + NodeMap found_elements = new NodeMap(); foreach (var element_kvp in Elements) { @@ -58,9 +57,9 @@ public Dictionary GetForTeam(PlayerTeam team) ///
/// The unit name. /// The minimap elements. - public Dictionary GetByUnitName(string unit_name) + public NodeMap GetByUnitName(string unit_name) { - Dictionary found_elements = new Dictionary(); + NodeMap found_elements = new NodeMap(); if (!string.IsNullOrEmpty(unit_name)) { @@ -83,5 +82,25 @@ public override string ToString() $"Elements: {Elements}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Minimap other && + Elements.Equals(other.Elements); + } + + /// + public override int GetHashCode() + { + int hashCode = 212298410; + hashCode = hashCode * -927590333 + Elements.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/MinimapProvider/MinimapElement.cs b/Dota2GSI/Nodes/MinimapProvider/MinimapElement.cs index 58f8dcf..5ae08b7 100644 --- a/Dota2GSI/Nodes/MinimapProvider/MinimapElement.cs +++ b/Dota2GSI/Nodes/MinimapProvider/MinimapElement.cs @@ -81,5 +81,41 @@ public override string ToString() $"VisionRange: {VisionRange}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is MinimapElement other && + Location.Equals(other.Location) && + RemainingTime.Equals(other.RemainingTime) && + EventDuration.Equals(other.EventDuration) && + Image.Equals(other.Image) && + Team.Equals(other.Team) && + Name.Equals(other.Name) && + Rotation.Equals(other.Rotation) && + UnitName.Equals(other.UnitName) && + VisionRange.Equals(other.VisionRange); + } + + /// + public override int GetHashCode() + { + int hashCode = 111327035; + hashCode = hashCode * -71571866 + Location.GetHashCode(); + hashCode = hashCode * -71571866 + RemainingTime.GetHashCode(); + hashCode = hashCode * -71571866 + EventDuration.GetHashCode(); + hashCode = hashCode * -71571866 + Image.GetHashCode(); + hashCode = hashCode * -71571866 + Team.GetHashCode(); + hashCode = hashCode * -71571866 + Name.GetHashCode(); + hashCode = hashCode * -71571866 + Rotation.GetHashCode(); + hashCode = hashCode * -71571866 + UnitName.GetHashCode(); + hashCode = hashCode * -71571866 + VisionRange.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/NeutralItems.cs b/Dota2GSI/Nodes/NeutralItems.cs index 56d2588..04eca22 100644 --- a/Dota2GSI/Nodes/NeutralItems.cs +++ b/Dota2GSI/Nodes/NeutralItems.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.NeutralItemsProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -14,12 +13,12 @@ public class NeutralItems : Node /// /// Information about various neutral item tiers. /// - public readonly Dictionary TierInfos = new Dictionary(); + public readonly NodeMap TierInfos = new NodeMap(); /// /// Information about team's neutral items. /// - public readonly Dictionary TeamItems = new Dictionary(); + public readonly NodeMap TeamItems = new NodeMap(); private Regex _tier_id_regex = new Regex(@"tier(\d+)"); private Regex _team_id_regex = new Regex(@"team(\d+)"); @@ -81,5 +80,27 @@ public override string ToString() $"TeamItems: {TeamItems}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is NeutralItems other && + TierInfos.Equals(other.TierInfos) && + TeamItems.Equals(other.TeamItems); + } + + /// + public override int GetHashCode() + { + int hashCode = 904745338; + hashCode = hashCode * -700564887 + TierInfos.GetHashCode(); + hashCode = hashCode * -700564887 + TeamItems.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/NeutralItemsProvider/NeutralItem.cs b/Dota2GSI/Nodes/NeutralItemsProvider/NeutralItem.cs index a871e18..cddbd9a 100644 --- a/Dota2GSI/Nodes/NeutralItemsProvider/NeutralItem.cs +++ b/Dota2GSI/Nodes/NeutralItemsProvider/NeutralItem.cs @@ -98,5 +98,33 @@ public override string ToString() $"PlayerID: {PlayerID}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is NeutralItem other && + Name.Equals(other.Name) && + Tier.Equals(other.Tier) && + Charges.Equals(other.Charges) && + State.Equals(other.State) && + PlayerID.Equals(other.PlayerID); + } + + /// + public override int GetHashCode() + { + int hashCode = 905733096; + hashCode = hashCode * -66160412 + Name.GetHashCode(); + hashCode = hashCode * -66160412 + Tier.GetHashCode(); + hashCode = hashCode * -66160412 + Charges.GetHashCode(); + hashCode = hashCode * -66160412 + State.GetHashCode(); + hashCode = hashCode * -66160412 + PlayerID.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/NeutralItemsProvider/NeutralTierInfo.cs b/Dota2GSI/Nodes/NeutralItemsProvider/NeutralTierInfo.cs index 201af31..bf1713b 100644 --- a/Dota2GSI/Nodes/NeutralItemsProvider/NeutralTierInfo.cs +++ b/Dota2GSI/Nodes/NeutralItemsProvider/NeutralTierInfo.cs @@ -38,5 +38,29 @@ public override string ToString() $"DropAfterTime: {DropAfterTime}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is NeutralTierInfo other && + Tier.Equals(other.Tier) && + MaxCount.Equals(other.MaxCount) && + DropAfterTime.Equals(other.DropAfterTime); + } + + /// + public override int GetHashCode() + { + int hashCode = 919325865; + hashCode = hashCode * -426172320 + Tier.GetHashCode(); + hashCode = hashCode * -426172320 + MaxCount.GetHashCode(); + hashCode = hashCode * -426172320 + DropAfterTime.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/NeutralItemsProvider/TeamNeutralItems.cs b/Dota2GSI/Nodes/NeutralItemsProvider/TeamNeutralItems.cs index 9b0804e..d56d41a 100644 --- a/Dota2GSI/Nodes/NeutralItemsProvider/TeamNeutralItems.cs +++ b/Dota2GSI/Nodes/NeutralItemsProvider/TeamNeutralItems.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes.NeutralItemsProvider @@ -18,7 +17,7 @@ public class TeamNeutralItems : Node /// /// The team neutral items. /// - public readonly Dictionary> TeamItems = new Dictionary>(); + public readonly NodeMap> TeamItems = new NodeMap>(); private Regex _tier_id_regex = new Regex(@"tier(\d+)"); private Regex _item_id_regex = new Regex(@"item(\d+)"); @@ -33,7 +32,7 @@ internal TeamNeutralItems(JObject parsed_data = null) : base(parsed_data) if (!TeamItems.ContainsKey(tier_index)) { - TeamItems.Add(tier_index, new Dictionary()); + TeamItems.Add(tier_index, new NodeMap()); } GetMatchingObjects(obj, _item_id_regex, (Match sub_match, JObject sub_obj) => @@ -61,5 +60,27 @@ public override string ToString() $"TeamItems: {TeamItems}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is TeamNeutralItems other && + ItemsFound.Equals(other.ItemsFound) && + TeamItems.Equals(other.TeamItems); + } + + /// + public override int GetHashCode() + { + int hashCode = 799436177; + hashCode = hashCode * -132257912 + ItemsFound.GetHashCode(); + hashCode = hashCode * -132257912 + TeamItems.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Node.cs b/Dota2GSI/Nodes/Node.cs index 07763bc..ec3a50f 100644 --- a/Dota2GSI/Nodes/Node.cs +++ b/Dota2GSI/Nodes/Node.cs @@ -1,5 +1,7 @@ -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -24,6 +26,22 @@ internal Node(JObject parsed_data) _ParsedData = parsed_data; } + internal T ToEnum(string str) + { + if (!string.IsNullOrWhiteSpace(str)) + { + try + { + return (T)Enum.Parse(typeof(T), str, true); + } + catch + { + } + } + + return (T)Enum.Parse(typeof(T), "Undefined", true); + } + internal JToken GetJToken(string property_name) { if (_ParsedData != null) @@ -118,18 +136,7 @@ internal T GetEnum(string property_name) { var string_value = GetString(property_name); - if (!string.IsNullOrWhiteSpace(string_value)) - { - try - { - return (T)Enum.Parse(typeof(T), string_value, true); - } - catch - { - } - } - - return (T)Enum.Parse(typeof(T), "Undefined", true); + return ToEnum(string_value); } internal bool GetBool(string Name) @@ -216,5 +223,157 @@ public virtual bool IsValid() { return _successfully_retrieved_any_value; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Node other && + _ParsedData.Equals(other._ParsedData) && + _successfully_retrieved_any_value.Equals(other._successfully_retrieved_any_value); + } + + /// + public override int GetHashCode() + { + int hashCode = 898763153; + hashCode = hashCode * -405816372 + _ParsedData.GetHashCode(); + hashCode = hashCode * -405816372 + _successfully_retrieved_any_value.GetHashCode(); + return hashCode; + } + } + + /// + /// Helper class,
+ /// A Dictionary class wrapped with proper Equals and GetHashCode functionality.
+ /// Can be safely cast into Dictionary. + ///
+ /// Key type + /// Value type + public class NodeMap : Dictionary + { + public NodeMap() + { + } + + public NodeMap(Dictionary dictionary) + { + foreach (var kvp in dictionary) + { + Add(kvp.Key, kvp.Value); + } + } + + /// + public override string ToString() + { + string return_string = ""; + return_string += "["; + bool first_element = true; + foreach (var kvp in this) + { + if (first_element) + { + first_element = false; + } + else + { + return_string += ", "; + } + + return_string += $"{kvp.Key}: {kvp.Value}"; + } + return_string += "]"; + return return_string; + } + + /// + public override bool Equals(object obj) + { + return obj is NodeMap other && + this.SequenceEqual(other); + } + + /// + public override int GetHashCode() + { + int hashCode = 482146548; + + foreach (var kvp in this) + { + hashCode = hashCode * -954782166 + kvp.Key.GetHashCode(); + hashCode = hashCode * -954782166 + kvp.Value.GetHashCode(); + } + + return hashCode; + } + } + + /// + /// Helper class,
+ /// A List class wrapped with proper Equals and GetHashCode functionality.
+ /// Can be safely cast into List. + ///
+ /// Value type + public class NodeList : List + { + public NodeList() + { + } + + public NodeList(IEnumerable list) + { + foreach (var item in list) + { + Add(item); + } + } + + /// + public override string ToString() + { + string return_string = ""; + return_string += "["; + bool first_element = true; + foreach (var item in this) + { + if (first_element) + { + first_element = false; + } + else + { + return_string += ", "; + } + + return_string += $"{item}"; + } + return_string += "]"; + return return_string; + } + + /// + public override bool Equals(object obj) + { + return obj is NodeList other && + this.SequenceEqual(other); + } + + /// + public override int GetHashCode() + { + int hashCode = 825543184; + + foreach (var item in this) + { + hashCode = hashCode * -357544921 + item.GetHashCode(); + } + + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Player.cs b/Dota2GSI/Nodes/Player.cs index a3d903f..e311e5f 100644 --- a/Dota2GSI/Nodes/Player.cs +++ b/Dota2GSI/Nodes/Player.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.PlayerProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -19,7 +18,7 @@ public class Player : Node /// /// The team player's details. (SPECTATOR ONLY) /// - public readonly Dictionary> Teams = new Dictionary>(); + public readonly NodeMap> Teams = new NodeMap>(); private Regex _team_id_regex = new Regex(@"team(\d+)"); private Regex _player_id_regex = new Regex(@"player(\d+)"); @@ -36,7 +35,7 @@ internal Player(JObject parsed_data = null) : base(parsed_data) if (!Teams.ContainsKey(team_id)) { - Teams.Add(team_id, new Dictionary()); + Teams.Add(team_id, new NodeMap()); } GetMatchingObjects(obj, _player_id_regex, (Match sub_match, JObject sub_obj) => @@ -61,14 +60,14 @@ internal Player(JObject parsed_data = null) : base(parsed_data) ///
/// The team. /// A dictionary of player id mapped to their player details. - public Dictionary GetForTeam(PlayerTeam team) + public NodeMap GetForTeam(PlayerTeam team) { if (Teams.ContainsKey(team)) { return Teams[team]; } - return new Dictionary(); + return new NodeMap(); } /// @@ -106,5 +105,27 @@ public override bool IsValid() { return LocalPlayer.IsValid() || base.IsValid(); } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Player other && + LocalPlayer.Equals(other.LocalPlayer) && + Teams.Equals(other.Teams); + } + + /// + public override int GetHashCode() + { + int hashCode = 472630013; + hashCode = hashCode * -524857764 + LocalPlayer.GetHashCode(); + hashCode = hashCode * -524857764 + Teams.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/PlayerProvider/PlayerDetails.cs b/Dota2GSI/Nodes/PlayerProvider/PlayerDetails.cs index e35b1bb..c177f38 100644 --- a/Dota2GSI/Nodes/PlayerProvider/PlayerDetails.cs +++ b/Dota2GSI/Nodes/PlayerProvider/PlayerDetails.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes.PlayerProvider @@ -89,7 +88,7 @@ public class PlayerDetails : Node /// /// Player's list of kills. The index corresponds to the player no which can be used to find the playerdetails if in spectator mode using the Teams.AllPlayers property. /// - public readonly Dictionary KillList = new Dictionary(); + public readonly NodeMap KillList = new NodeMap(); /// /// Player's team. @@ -323,5 +322,101 @@ public override string ToString() $"CampsStacked: {CampsStacked}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is PlayerDetails other && + SteamID.Equals(other.SteamID) && + AccountID.Equals(other.AccountID) && + Name.Equals(other.Name) && + Activity.Equals(other.Activity) && + Kills.Equals(other.Kills) && + Deaths.Equals(other.Deaths) && + Assists.Equals(other.Assists) && + LastHits.Equals(other.LastHits) && + Denies.Equals(other.Denies) && + KillStreak.Equals(other.KillStreak) && + CommandsIssued.Equals(other.CommandsIssued) && + KillList.Equals(other.KillList) && + Team.Equals(other.Team) && + PlayerSlot.Equals(other.PlayerSlot) && + PlayerTeamSlot.Equals(other.PlayerTeamSlot) && + Gold.Equals(other.Gold) && + GoldReliable.Equals(other.GoldReliable) && + GoldUnreliable.Equals(other.GoldUnreliable) && + GoldFromHeroKills.Equals(other.GoldFromHeroKills) && + GoldFromCreepKills.Equals(other.GoldFromCreepKills) && + GoldFromIncome.Equals(other.GoldFromIncome) && + GoldFromShared.Equals(other.GoldFromShared) && + GoldPerMinute.Equals(other.GoldPerMinute) && + ExperiencePerMinute.Equals(other.ExperiencePerMinute) && + OnstageSeat.Equals(other.OnstageSeat) && + NetWorth.Equals(other.NetWorth) && + HeroDamage.Equals(other.HeroDamage) && + HeroHealing.Equals(other.HeroHealing) && + TowerDamage.Equals(other.TowerDamage) && + SupportGoldSpent.Equals(other.SupportGoldSpent) && + ConsumableGoldSpent.Equals(other.ConsumableGoldSpent) && + ItemGoldSpent.Equals(other.ItemGoldSpent) && + GoldLostToDeath.Equals(other.GoldLostToDeath) && + GoldSpentOnBuybacks.Equals(other.GoldSpentOnBuybacks) && + WardsPurchased.Equals(other.WardsPurchased) && + WardsPlaced.Equals(other.WardsPlaced) && + WardsDestroyed.Equals(other.WardsDestroyed) && + RunesActivated.Equals(other.RunesActivated) && + CampsStacked.Equals(other.CampsStacked); + } + + /// + public override int GetHashCode() + { + int hashCode = 602668635; + hashCode = hashCode * -112730515 + SteamID.GetHashCode(); + hashCode = hashCode * -112730515 + AccountID.GetHashCode(); + hashCode = hashCode * -112730515 + Name.GetHashCode(); + hashCode = hashCode * -112730515 + Activity.GetHashCode(); + hashCode = hashCode * -112730515 + Kills.GetHashCode(); + hashCode = hashCode * -112730515 + Deaths.GetHashCode(); + hashCode = hashCode * -112730515 + Assists.GetHashCode(); + hashCode = hashCode * -112730515 + LastHits.GetHashCode(); + hashCode = hashCode * -112730515 + Denies.GetHashCode(); + hashCode = hashCode * -112730515 + KillStreak.GetHashCode(); + hashCode = hashCode * -112730515 + CommandsIssued.GetHashCode(); + hashCode = hashCode * -112730515 + KillList.GetHashCode(); + hashCode = hashCode * -112730515 + Team.GetHashCode(); + hashCode = hashCode * -112730515 + PlayerSlot.GetHashCode(); + hashCode = hashCode * -112730515 + PlayerTeamSlot.GetHashCode(); + hashCode = hashCode * -112730515 + Gold.GetHashCode(); + hashCode = hashCode * -112730515 + GoldReliable.GetHashCode(); + hashCode = hashCode * -112730515 + GoldUnreliable.GetHashCode(); + hashCode = hashCode * -112730515 + GoldFromHeroKills.GetHashCode(); + hashCode = hashCode * -112730515 + GoldFromCreepKills.GetHashCode(); + hashCode = hashCode * -112730515 + GoldFromIncome.GetHashCode(); + hashCode = hashCode * -112730515 + GoldFromShared.GetHashCode(); + hashCode = hashCode * -112730515 + GoldPerMinute.GetHashCode(); + hashCode = hashCode * -112730515 + ExperiencePerMinute.GetHashCode(); + hashCode = hashCode * -112730515 + OnstageSeat.GetHashCode(); + hashCode = hashCode * -112730515 + NetWorth.GetHashCode(); + hashCode = hashCode * -112730515 + HeroDamage.GetHashCode(); + hashCode = hashCode * -112730515 + HeroHealing.GetHashCode(); + hashCode = hashCode * -112730515 + TowerDamage.GetHashCode(); + hashCode = hashCode * -112730515 + SupportGoldSpent.GetHashCode(); + hashCode = hashCode * -112730515 + ConsumableGoldSpent.GetHashCode(); + hashCode = hashCode * -112730515 + ItemGoldSpent.GetHashCode(); + hashCode = hashCode * -112730515 + GoldLostToDeath.GetHashCode(); + hashCode = hashCode * -112730515 + GoldSpentOnBuybacks.GetHashCode(); + hashCode = hashCode * -112730515 + WardsPurchased.GetHashCode(); + hashCode = hashCode * -112730515 + WardsPlaced.GetHashCode(); + hashCode = hashCode * -112730515 + WardsDestroyed.GetHashCode(); + hashCode = hashCode * -112730515 + RunesActivated.GetHashCode(); + hashCode = hashCode * -112730515 + CampsStacked.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Provider.cs b/Dota2GSI/Nodes/Provider.cs index b5bb9cd..51901c2 100644 --- a/Dota2GSI/Nodes/Provider.cs +++ b/Dota2GSI/Nodes/Provider.cs @@ -44,6 +44,32 @@ public override string ToString() $"Version: {Version}, " + $"TimeStamp: {TimeStamp}" + $"]"; + } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Provider other && + Name.Equals(other.Name) && + AppID.Equals(other.AppID) && + Version.Equals(other.Version) && + TimeStamp.Equals(other.TimeStamp); + } + + /// + public override int GetHashCode() + { + int hashCode = 464630987; + hashCode = hashCode * -987549067 + Name.GetHashCode(); + hashCode = hashCode * -987549067 + AppID.GetHashCode(); + hashCode = hashCode * -987549067 + Version.GetHashCode(); + hashCode = hashCode * -987549067 + TimeStamp.GetHashCode(); + return hashCode; } } } diff --git a/Dota2GSI/Nodes/Roshan.cs b/Dota2GSI/Nodes/Roshan.cs index 33a6006..f54f45c 100644 --- a/Dota2GSI/Nodes/Roshan.cs +++ b/Dota2GSI/Nodes/Roshan.cs @@ -75,5 +75,39 @@ public override string ToString() $"Drops: {Drops}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Roshan other && + Health.Equals(other.Health) && + MaxHealth.Equals(other.MaxHealth) && + IsAlive.Equals(other.IsAlive) && + SpawnPhase.Equals(other.SpawnPhase) && + PhaseTimeRemaining.Equals(other.PhaseTimeRemaining) && + Location.Equals(other.Location) && + Rotation.Equals(other.Rotation) && + Drops.Equals(other.Drops); + } + + /// + public override int GetHashCode() + { + int hashCode = 289605977; + hashCode = hashCode * -839892812 + Health.GetHashCode(); + hashCode = hashCode * -839892812 + MaxHealth.GetHashCode(); + hashCode = hashCode * -839892812 + IsAlive.GetHashCode(); + hashCode = hashCode * -839892812 + SpawnPhase.GetHashCode(); + hashCode = hashCode * -839892812 + PhaseTimeRemaining.GetHashCode(); + hashCode = hashCode * -839892812 + Location.GetHashCode(); + hashCode = hashCode * -839892812 + Rotation.GetHashCode(); + hashCode = hashCode * -839892812 + Drops.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/RoshanProvider/ItemsDrop.cs b/Dota2GSI/Nodes/RoshanProvider/ItemsDrop.cs index 86477fe..1c7005d 100644 --- a/Dota2GSI/Nodes/RoshanProvider/ItemsDrop.cs +++ b/Dota2GSI/Nodes/RoshanProvider/ItemsDrop.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes.RoshanProvider @@ -13,7 +12,7 @@ public class ItemsDrop : Node /// /// Items that can drop. /// - public readonly Dictionary Items = new Dictionary(); + public readonly NodeMap Items = new NodeMap(); private Regex _item_regex = new Regex(@"item(\d+)"); internal ItemsDrop(JObject parsed_data = null) : base(parsed_data) @@ -33,5 +32,25 @@ public override string ToString() $"Items: {Items}" + $"]"; } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is ItemsDrop other && + Items.Equals(other.Items); + } + + /// + public override int GetHashCode() + { + int hashCode = 771247438; + hashCode = hashCode * -122023451 + Items.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/Wearables.cs b/Dota2GSI/Nodes/Wearables.cs index abee127..fa9b613 100644 --- a/Dota2GSI/Nodes/Wearables.cs +++ b/Dota2GSI/Nodes/Wearables.cs @@ -1,7 +1,6 @@ using Dota2GSI.Nodes.WearablesProvider; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes @@ -19,7 +18,7 @@ public class Wearables : Node /// /// The team players wearables. (SPECTATOR ONLY) /// - public readonly Dictionary> Teams = new Dictionary>(); + public readonly NodeMap> Teams = new NodeMap>(); private Regex _team_id_regex = new Regex(@"team(\d+)"); private Regex _player_id_regex = new Regex(@"player(\d+)"); @@ -36,7 +35,7 @@ internal Wearables(JObject parsed_data = null) : base(parsed_data) if (!Teams.ContainsKey(team_id)) { - Teams.Add(team_id, new Dictionary()); + Teams.Add(team_id, new NodeMap()); } GetMatchingObjects(parsed_data, _player_id_regex, (Match sub_match, JObject sub_obj) => @@ -62,14 +61,14 @@ internal Wearables(JObject parsed_data = null) : base(parsed_data) /// /// The team. /// A dictionary of player id mapped to their wearables. - public Dictionary GetForTeam(PlayerTeam team) + public NodeMap GetForTeam(PlayerTeam team) { if (Teams.ContainsKey(team)) { return Teams[team]; } - return new Dictionary(); + return new NodeMap(); } /// @@ -107,5 +106,27 @@ public override bool IsValid() { return LocalPlayer.IsValid() || base.IsValid(); } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is Wearables other && + LocalPlayer.Equals(other.LocalPlayer) && + Teams.Equals(other.Teams); + } + + /// + public override int GetHashCode() + { + int hashCode = 894234507; + hashCode = hashCode * -274381118 + LocalPlayer.GetHashCode(); + hashCode = hashCode * -274381118 + Teams.GetHashCode(); + return hashCode; + } } } diff --git a/Dota2GSI/Nodes/WearablesProvider/PlayerWearables.cs b/Dota2GSI/Nodes/WearablesProvider/PlayerWearables.cs index 1f38d03..6330760 100644 --- a/Dota2GSI/Nodes/WearablesProvider/PlayerWearables.cs +++ b/Dota2GSI/Nodes/WearablesProvider/PlayerWearables.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Text.RegularExpressions; namespace Dota2GSI.Nodes.WearablesProvider @@ -13,7 +12,7 @@ public class PlayerWearables : Node /// /// The dictionary of player's wearable items. /// - public readonly Dictionary Wearables = new Dictionary(); + public readonly NodeMap Wearables = new NodeMap(); private Regex _wearable_regex = new Regex(@"wearable(\d+)"); private Regex _style_regex = new Regex(@"style(\d+)"); @@ -57,6 +56,26 @@ public override string ToString() return $"[" + $"Wearables: {Wearables}" + $"]"; + } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is PlayerWearables other && + Wearables.Equals(other.Wearables); + } + + /// + public override int GetHashCode() + { + int hashCode = 954667287; + hashCode = hashCode * -711254284 + Wearables.GetHashCode(); + return hashCode; } } } diff --git a/Dota2GSI/Nodes/WearablesProvider/WearableItem.cs b/Dota2GSI/Nodes/WearablesProvider/WearableItem.cs index 4ef64d2..e3361b1 100644 --- a/Dota2GSI/Nodes/WearablesProvider/WearableItem.cs +++ b/Dota2GSI/Nodes/WearablesProvider/WearableItem.cs @@ -29,6 +29,28 @@ public override string ToString() $"ID: {ID}, " + $"Style: {Style}" + $"]"; + } + + /// + public override bool Equals(object obj) + { + if (null == obj) + { + return false; + } + + return obj is WearableItem other && + ID.Equals(other.ID) && + Style.Equals(other.Style); + } + + /// + public override int GetHashCode() + { + int hashCode = 652470603; + hashCode = hashCode * -397083684 + ID.GetHashCode(); + hashCode = hashCode * -397083684 + Style.GetHashCode(); + return hashCode; } } } diff --git a/Dota2GSI/StateHandlers/AbilitiesHandler.cs b/Dota2GSI/StateHandlers/AbilitiesHandler.cs new file mode 100644 index 0000000..56563b8 --- /dev/null +++ b/Dota2GSI/StateHandlers/AbilitiesHandler.cs @@ -0,0 +1,108 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes.AbilitiesProvider; +using Dota2GSI.Nodes.Helpers; +using System.Collections.Generic; +using System.Linq; + +namespace Dota2GSI +{ + public class AbilitiesHandler : EventHandler + { + private Dictionary _player_cache = new Dictionary(); + + public AbilitiesHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnAbilitiesUpdated); + dispatcher.Subscribe(OnAbilityDetailsChanged); + } + + ~AbilitiesHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnAbilitiesUpdated); + dispatcher.Unsubscribe(OnAbilityDetailsChanged); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + + _player_cache[evt.New.PlayerID] = evt.New; + } + + private void OnAbilitiesUpdated(DotaGameEvent e) + { + AbilitiesUpdated evt = (e as AbilitiesUpdated); + + if (evt == null) + { + return; + } + + var local_player_details = _player_cache.GetValueOrDefault(-1, null); + + if (!evt.New.LocalPlayer.Equals(evt.Previous.LocalPlayer) && local_player_details != null) + { + dispatcher.Broadcast(new AbilityDetailsChanged(evt.New.LocalPlayer, evt.Previous.LocalPlayer, local_player_details)); + } + + foreach (var team_kvp in evt.New.Teams) + { + foreach (var player_kvp in team_kvp.Value) + { + // Get corresponding previous hero details. + var previous_ability_details = evt.Previous.GetForPlayer(player_kvp.Key); + + var player_details = _player_cache.GetValueOrDefault(player_kvp.Key, null); + + if (!player_kvp.Value.Equals(previous_ability_details) && previous_ability_details.IsValid() && player_details != null) + { + dispatcher.Broadcast(new AbilityDetailsChanged(player_kvp.Value, previous_ability_details, player_details)); + } + } + } + } + + private void OnAbilityDetailsChanged(DotaGameEvent e) + { + AbilityDetailsChanged evt = (e as AbilityDetailsChanged); + + if (evt == null) + { + return; + } + + foreach (var ability in evt.New) + { + Ability found_ability = evt.Previous.FirstOrDefault((Ability element) => { return element.Name.Equals(ability.Name); }, null); + + if (found_ability == null) + { + dispatcher.Broadcast(new AbilityAdded(ability, evt.Player)); + continue; + } + + if (!ability.Equals(found_ability)) + { + dispatcher.Broadcast(new AbilityUpdated(ability, found_ability, evt.Player)); + } + } + + foreach (var ability in evt.Previous) + { + Ability found_ability = evt.New.FirstOrDefault((Ability element) => { return element.Name.Equals(ability.Name); }, null); + + if (found_ability == null) + { + dispatcher.Broadcast(new AbilityRemoved(ability, evt.Player)); + } + } + } + } +} diff --git a/Dota2GSI/StateHandlers/AuthHandler.cs b/Dota2GSI/StateHandlers/AuthHandler.cs new file mode 100644 index 0000000..4809b95 --- /dev/null +++ b/Dota2GSI/StateHandlers/AuthHandler.cs @@ -0,0 +1,27 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class AuthHandler : EventHandler + { + public AuthHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnAuthUpdated); + } + + ~AuthHandler() + { + dispatcher.Unsubscribe(OnAuthUpdated); + } + + private void OnAuthUpdated(DotaGameEvent e) + { + AuthUpdated evt = (e as AuthUpdated); + + if (evt == null) + { + return; + } + } + } +} diff --git a/Dota2GSI/StateHandlers/BuildingsHandler.cs b/Dota2GSI/StateHandlers/BuildingsHandler.cs new file mode 100644 index 0000000..383d6e4 --- /dev/null +++ b/Dota2GSI/StateHandlers/BuildingsHandler.cs @@ -0,0 +1,213 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes; +using Dota2GSI.Nodes.BuildingsProvider; +using System; +using System.Collections.Generic; + +namespace Dota2GSI +{ + public class BuildingsHandler : EventHandler + { + public BuildingsHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnBuildingsUpdated); + dispatcher.Subscribe(OnBuildingsLayoutUpdated); + dispatcher.Subscribe(OnTowerUpdated); + dispatcher.Subscribe(OnRacksUpdated); + dispatcher.Subscribe(OnAncientUpdated); + dispatcher.Subscribe(OnTeamBuildingUpdated); + } + + ~BuildingsHandler() + { + dispatcher.Unsubscribe(OnBuildingsUpdated); + dispatcher.Unsubscribe(OnBuildingsLayoutUpdated); + dispatcher.Unsubscribe(OnTowerUpdated); + dispatcher.Unsubscribe(OnRacksUpdated); + dispatcher.Unsubscribe(OnAncientUpdated); + dispatcher.Unsubscribe(OnTeamBuildingUpdated); + } + + private void OnBuildingsUpdated(DotaGameEvent e) + { + BuildingsUpdated evt = (e as BuildingsUpdated); + + if (evt == null) + { + return; + } + + foreach (var building_kvp in evt.New.AllBuildings) + { + if (!evt.Previous.AllBuildings.ContainsKey(building_kvp.Key)) + { + continue; + } + + if (!building_kvp.Value.Equals(evt.Previous.AllBuildings[building_kvp.Key])) + { + dispatcher.Broadcast(new BuildingsLayoutUpdated(building_kvp.Value, evt.Previous.AllBuildings[building_kvp.Key], building_kvp.Key)); + } + } + } + + private void OnBuildingsLayoutUpdated(DotaGameEvent e) + { + BuildingsLayoutUpdated evt = (e as BuildingsLayoutUpdated); + + if (evt == null) + { + return; + } + + Dictionary, NodeMap>> towers = new Dictionary, NodeMap>>() + { + { BuildingLocation.TopLane, new Tuple, NodeMap>(evt.New.TopTowers, evt.Previous.TopTowers) }, + { BuildingLocation.MiddleLane, new Tuple, NodeMap>(evt.New.MiddleTowers, evt.Previous.MiddleTowers) }, + { BuildingLocation.BottomLane, new Tuple, NodeMap>(evt.New.BottomTowers, evt.Previous.BottomTowers) } + }; + + foreach (var tower_kvp in towers) + { + if (!tower_kvp.Value.Item1.Equals(tower_kvp.Value.Item2)) + { + foreach (var building_kvp in tower_kvp.Value.Item1) + { + if (!tower_kvp.Value.Item2.ContainsKey(building_kvp.Key)) + { + // Not much point in having "TowerAdded" event for Dota gameplay. + continue; + } + + var previous_building = tower_kvp.Value.Item2[building_kvp.Key]; + + if (!building_kvp.Value.Equals(previous_building)) + { + dispatcher.Broadcast(new TowerUpdated(building_kvp.Value, previous_building, "", evt.Team, tower_kvp.Key)); + } + } + } + } + + Dictionary, NodeMap>> racks = new Dictionary, NodeMap>>() + { + { BuildingLocation.TopLane, new Tuple, NodeMap>(evt.New.TopRacks, evt.Previous.TopRacks) }, + { BuildingLocation.MiddleLane, new Tuple, NodeMap>(evt.New.MiddleRacks, evt.Previous.MiddleRacks) }, + { BuildingLocation.BottomLane, new Tuple, NodeMap>(evt.New.BottomRacks, evt.Previous.BottomRacks) } + }; + + foreach (var rack_kvp in racks) + { + if (!rack_kvp.Value.Item1.Equals(rack_kvp.Value.Item2)) + { + foreach (var building_kvp in rack_kvp.Value.Item1) + { + if (!rack_kvp.Value.Item2.ContainsKey(building_kvp.Key)) + { + // Not much point in having "RacksAdded" event for Dota gameplay. + continue; + } + + var previous_building = rack_kvp.Value.Item2[building_kvp.Key]; + + if (!building_kvp.Value.Equals(previous_building)) + { + dispatcher.Broadcast(new RacksUpdated(building_kvp.Value, previous_building, building_kvp.Key, "", evt.Team, rack_kvp.Key)); + } + } + } + } + + if (!evt.New.Ancient.Equals(evt.Previous.Ancient)) + { + BuildingLocation location = BuildingLocation.Base; + + if (!evt.New.Ancient.Equals(evt.Previous.Ancient)) + { + dispatcher.Broadcast(new AncientUpdated(evt.New.Ancient, evt.Previous.Ancient, "", evt.Team, location)); + } + } + + if (!evt.New.OtherBuildings.Equals(evt.Previous.OtherBuildings)) + { + BuildingLocation location = BuildingLocation.Undefined; + + foreach (var building_kvp in evt.New.OtherBuildings) + { + if (!evt.Previous.OtherBuildings.ContainsKey(building_kvp.Key)) + { + // Not much point in having "OtherBuildingsAdded" event for Dota gameplay. + continue; + } + + var previous_building = evt.Previous.OtherBuildings[building_kvp.Key]; + + if (!building_kvp.Value.Equals(previous_building)) + { + dispatcher.Broadcast(new TeamBuildingUpdated(building_kvp.Value, previous_building, building_kvp.Key, evt.Team, location)); + } + } + } + } + + private void OnTowerUpdated(DotaGameEvent e) + { + TowerUpdated evt = (e as TowerUpdated); + + if (evt == null) + { + return; + } + + if (evt.New.Health == 0 && evt.Previous.Health > 0) + { + dispatcher.Broadcast(new TowerDestroyed(evt.New, evt.Previous, evt.EntityID, evt.Team, evt.Location)); + } + } + + private void OnRacksUpdated(DotaGameEvent e) + { + RacksUpdated evt = (e as RacksUpdated); + + if (evt == null) + { + return; + } + + if (evt.New.Health == 0 && evt.Previous.Health > 0) + { + dispatcher.Broadcast(new RacksDestroyed(evt.New, evt.Previous, evt.RacksType, evt.EntityID, evt.Team, evt.Location)); + } + } + + private void OnAncientUpdated(DotaGameEvent e) + { + AncientUpdated evt = (e as AncientUpdated); + + if (evt == null) + { + return; + } + + if (evt.New.Health == 0 && evt.Previous.Health > 0) + { + dispatcher.Broadcast(new AncientDestroyed(evt.New, evt.Previous, evt.EntityID, evt.Team, evt.Location)); + } + } + + private void OnTeamBuildingUpdated(DotaGameEvent e) + { + TeamBuildingUpdated evt = (e as TeamBuildingUpdated); + + if (evt == null) + { + return; + } + + if (evt.New.Health == 0 && evt.Previous.Health > 0) + { + dispatcher.Broadcast(new TeamBuildingDestroyed(evt.New, evt.Previous, evt.EntityID, evt.Team, evt.Location)); + } + } + } +} diff --git a/Dota2GSI/StateHandlers/CouriersHandler.cs b/Dota2GSI/StateHandlers/CouriersHandler.cs new file mode 100644 index 0000000..e82a74d --- /dev/null +++ b/Dota2GSI/StateHandlers/CouriersHandler.cs @@ -0,0 +1,97 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes.Helpers; +using System.Collections.Generic; + +namespace Dota2GSI +{ + public class CouriersHandler : EventHandler + { + private Dictionary _player_cache = new Dictionary(); + + public CouriersHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnCouriersUpdated); + dispatcher.Subscribe(OnCourierUpdated); + } + + ~CouriersHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnCouriersUpdated); + dispatcher.Unsubscribe(OnCourierUpdated); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + + _player_cache[evt.New.PlayerID] = evt.New; + } + + private void OnCouriersUpdated(DotaGameEvent e) + { + CouriersUpdated evt = (e as CouriersUpdated); + + if (evt == null) + { + return; + } + + foreach (var courier_kvp in evt.New.CouriersMap) + { + var previous_courier = evt.Previous.GetForPlayer(courier_kvp.Value.OwnerID); + var player_details = _player_cache.GetValueOrDefault(courier_kvp.Value.OwnerID, null); + + if (!courier_kvp.Value.Equals(previous_courier) && player_details != null) + { + dispatcher.Broadcast(new CourierUpdated(courier_kvp.Value, previous_courier, player_details)); + + dispatcher.Broadcast(new TeamCourierUpdated(courier_kvp.Value, previous_courier, player_details.Details.Team)); + } + } + } + + private void OnCourierUpdated(DotaGameEvent e) + { + CourierUpdated evt = (e as CourierUpdated); + + if (evt == null) + { + return; + } + + foreach (var item in evt.New.Items) + { + if (!evt.Previous.InventoryContains(item.Value.Name)) + { + // Courier gained an item. + dispatcher.Broadcast(new CourierItemAdded(item.Value, evt.New, evt.Player)); + continue; + } + + var previous_item = evt.Previous.GetInventoryItem(item.Value.Name); + + if (!item.Value.Equals(previous_item)) + { + // Item updated. + dispatcher.Broadcast(new CourierItemUpdated(item.Value, previous_item, evt.New, evt.Player)); + } + } + + foreach (var item in evt.Previous.Items) + { + if (!evt.New.InventoryContains(item.Value.Name)) + { + // Courier lost an inventory item. + dispatcher.Broadcast(new CourierItemRemoved(item.Value, evt.New, evt.Player)); + } + } + } + } +} diff --git a/Dota2GSI/StateHandlers/DraftHandler.cs b/Dota2GSI/StateHandlers/DraftHandler.cs new file mode 100644 index 0000000..bd0fd32 --- /dev/null +++ b/Dota2GSI/StateHandlers/DraftHandler.cs @@ -0,0 +1,42 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class DraftHandler : EventHandler + { + public DraftHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnDraftUpdated); + } + + ~DraftHandler() + { + dispatcher.Unsubscribe(OnDraftUpdated); + } + + private void OnDraftUpdated(DotaGameEvent e) + { + DraftUpdated evt = (e as DraftUpdated); + + if (evt == null) + { + return; + } + + foreach (var team_kvp in evt.New.Teams) + { + if (!evt.Previous.Teams.ContainsKey(team_kvp.Key)) + { + continue; + } + + var previous_draft = evt.Previous.Teams[team_kvp.Key]; + + if (!team_kvp.Value.Equals(previous_draft)) + { + dispatcher.Broadcast(new TeamDraftDetailsUpdated(team_kvp.Value, previous_draft, team_kvp.Key)); + } + } + } + } +} diff --git a/Dota2GSI/StateHandlers/FullDetailsHandler.cs b/Dota2GSI/StateHandlers/FullDetailsHandler.cs new file mode 100644 index 0000000..f055643 --- /dev/null +++ b/Dota2GSI/StateHandlers/FullDetailsHandler.cs @@ -0,0 +1,65 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class FullDetailsHandler : EventHandler + { + public FullDetailsHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnFullTeamDetailsUpdated); + } + + ~FullDetailsHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnFullTeamDetailsUpdated); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + } + + private void OnFullTeamDetailsUpdated(DotaGameEvent e) + { + FullTeamDetailsUpdated evt = (e as FullTeamDetailsUpdated); + + if (evt == null) + { + return; + } + + foreach (var player_kvp in evt.New.Players) + { + if (!evt.Previous.Players.ContainsKey(player_kvp.Key)) + { + // The player did not exist before. + dispatcher.Broadcast(new PlayerConnected(player_kvp.Value)); + continue; + } + + var previous_player = evt.Previous.Players[player_kvp.Key]; + + if (!player_kvp.Value.Equals(previous_player)) + { + dispatcher.Broadcast(new FullPlayerDetailsUpdated(player_kvp.Value, previous_player)); + } + } + + foreach (var player_kvp in evt.Previous.Players) + { + if (!evt.New.Players.ContainsKey(player_kvp.Key)) + { + // The player does not exist anymore. + dispatcher.Broadcast(new PlayerDisconnected(player_kvp.Value)); + } + } + } + } +} diff --git a/Dota2GSI/StateHandlers/GameplayEventsHandler.cs b/Dota2GSI/StateHandlers/GameplayEventsHandler.cs new file mode 100644 index 0000000..fce9e86 --- /dev/null +++ b/Dota2GSI/StateHandlers/GameplayEventsHandler.cs @@ -0,0 +1,130 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes.EventsProvider; +using Dota2GSI.Nodes.Helpers; +using System.Collections.Generic; +using System.Linq; + +namespace Dota2GSI +{ + public class GameplayEventsHandler : EventHandler + { + private Dictionary _player_cache = new Dictionary(); + + public GameplayEventsHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnEventsUpdated); + } + + ~GameplayEventsHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnEventsUpdated); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + + _player_cache[evt.New.PlayerID] = evt.New; + } + + private void OnEventsUpdated(DotaGameEvent e) + { + EventsUpdated evt = (e as EventsUpdated); + + if (evt == null) + { + return; + } + + foreach (var game_event in evt.New) + { + Event found_event = evt.Previous.FirstOrDefault((Event element) => { return element.Equals(game_event); }, null); + + if (found_event == null) + { + dispatcher.Broadcast(new GameplayEvent(game_event)); + + switch (game_event.EventType) + { + case EventType.Courier_killed: + { + var killer_player_details = _player_cache.GetValueOrDefault(game_event.KillerPlayerID, null); + if (killer_player_details != null) + { + dispatcher.Broadcast(new PlayerGameplayEvent(game_event, killer_player_details)); + } + dispatcher.Broadcast(new TeamGameplayEvent(game_event, game_event.Team)); + } + break; + case EventType.Roshan_killed: + { + var killer_player_details = _player_cache.GetValueOrDefault(game_event.KillerPlayerID, null); + if (killer_player_details != null) + { + dispatcher.Broadcast(new PlayerGameplayEvent(game_event, killer_player_details)); + } + dispatcher.Broadcast(new TeamGameplayEvent(game_event, game_event.Team)); + } + break; + case EventType.Aegis_picked_up: + { + var player_details = _player_cache.GetValueOrDefault(game_event.PlayerID, null); + if (player_details != null) + { + dispatcher.Broadcast(new PlayerGameplayEvent(game_event, player_details)); + dispatcher.Broadcast(new TeamGameplayEvent(game_event, player_details.Details.Team)); + } + } + break; + case EventType.Aegis_denied: + { + var player_details = _player_cache.GetValueOrDefault(game_event.PlayerID, null); + if (player_details != null) + { + dispatcher.Broadcast(new PlayerGameplayEvent(game_event, player_details)); + dispatcher.Broadcast(new TeamGameplayEvent(game_event, player_details.Details.Team)); + } + } + break; + case EventType.Tip: + { + var player_details = _player_cache.GetValueOrDefault(game_event.PlayerID, null); + var tip_receiver_player_details = _player_cache.GetValueOrDefault(game_event.TipReceiverPlayerID, null); + if (player_details != null) + { + dispatcher.Broadcast(new PlayerGameplayEvent(game_event, player_details)); + } + + if (tip_receiver_player_details != null) + { + dispatcher.Broadcast(new PlayerGameplayEvent(game_event, tip_receiver_player_details)); + dispatcher.Broadcast(new TeamGameplayEvent(game_event, tip_receiver_player_details.Details.Team)); + } + } + break; + case EventType.Bounty_rune_pickup: + { + var player_details = _player_cache.GetValueOrDefault(game_event.PlayerID, null); + if (player_details != null) + { + dispatcher.Broadcast(new PlayerGameplayEvent(game_event, player_details)); + } + + dispatcher.Broadcast(new TeamGameplayEvent(game_event, game_event.Team)); + } + break; + default: + break; + } + } + } + } + } +} diff --git a/Dota2GSI/StateHandlers/HeroHandler.cs b/Dota2GSI/StateHandlers/HeroHandler.cs new file mode 100644 index 0000000..090a53a --- /dev/null +++ b/Dota2GSI/StateHandlers/HeroHandler.cs @@ -0,0 +1,131 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes.Helpers; +using System.Collections.Generic; + +namespace Dota2GSI +{ + public class HeroHandler : EventHandler + { + private Dictionary _player_cache = new Dictionary(); + + public HeroHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnHeroUpdated); + dispatcher.Subscribe(OnHeroDetailsChanged); + } + + ~HeroHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnHeroUpdated); + dispatcher.Unsubscribe(OnHeroDetailsChanged); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + + _player_cache[evt.New.PlayerID] = evt.New; + } + + private void OnHeroUpdated(DotaGameEvent e) + { + HeroUpdated evt = (e as HeroUpdated); + + if (evt == null) + { + return; + } + + var local_player_details = _player_cache.GetValueOrDefault(-1, null); + + if (!evt.New.LocalPlayer.Equals(evt.Previous.LocalPlayer) && local_player_details != null) + { + dispatcher.Broadcast(new HeroDetailsChanged(evt.New.LocalPlayer, evt.Previous.LocalPlayer, local_player_details)); + } + + foreach (var team_kvp in evt.New.Teams) + { + foreach (var player_kvp in team_kvp.Value) + { + var player_details = _player_cache.GetValueOrDefault(player_kvp.Key, null); + var previous_hero_details = evt.Previous.GetForPlayer(player_kvp.Key); + + if (!player_kvp.Value.Equals(previous_hero_details) && player_details != null) + { + dispatcher.Broadcast(new HeroDetailsChanged(player_kvp.Value, previous_hero_details, player_details)); + } + } + } + } + + private void OnHeroDetailsChanged(DotaGameEvent e) + { + HeroDetailsChanged evt = (e as HeroDetailsChanged); + + if (evt == null) + { + return; + } + + if (!evt.New.Level.Equals(evt.Previous.Level)) + { + dispatcher.Broadcast(new HeroLevelChanged(evt.New.Level, evt.Previous.Level, evt.Player)); + } + + if (!evt.New.Health.Equals(evt.Previous.Health)) + { + dispatcher.Broadcast(new HeroHealthChanged(evt.New.Health, evt.Previous.Health, evt.Player)); + + if (!evt.New.IsAlive && evt.Previous.IsAlive) + { + dispatcher.Broadcast(new HeroDied(evt.New.Health, evt.Previous.Health, evt.Player)); + } + else if (evt.New.IsAlive && !evt.Previous.IsAlive) + { + dispatcher.Broadcast(new HeroRespawned(evt.New.Health, evt.Previous.Health, evt.Player)); + } + else if (evt.New.IsAlive && evt.Previous.IsAlive) + { + dispatcher.Broadcast(new HeroTookDamage(evt.New.Health, evt.Previous.Health, evt.Player)); + } + } + + if (!evt.New.Mana.Equals(evt.Previous.Mana)) + { + dispatcher.Broadcast(new HeroManaChanged(evt.New.Mana, evt.Previous.Mana, evt.Player)); + } + + if (!evt.New.HeroState.Equals(evt.Previous.HeroState)) + { + dispatcher.Broadcast(new HeroStateChanged(evt.New.HeroState, evt.Previous.HeroState, evt.Player)); + } + + if (!evt.New.IsMuted.Equals(evt.Previous.IsMuted)) + { + dispatcher.Broadcast(new HeroMuteStateChanged(evt.New.IsMuted, evt.Previous.IsMuted, evt.Player)); + } + + if (!evt.New.SelectedUnit.Equals(evt.Previous.SelectedUnit)) + { + dispatcher.Broadcast(new HeroSelectedChanged(evt.New.SelectedUnit, evt.Previous.SelectedUnit, evt.Player)); + } + + if (!evt.New.TalentTree.Equals(evt.Previous.TalentTree)) + { + dispatcher.Broadcast(new HeroTalentTreeChanged(evt.New.TalentTree, evt.Previous.TalentTree, evt.Player)); + } + + if (!evt.New.AttributesLevel.Equals(evt.Previous.AttributesLevel)) + { + dispatcher.Broadcast(new HeroAttributesLevelChanged(evt.New.AttributesLevel, evt.Previous.AttributesLevel, evt.Player)); + } + } + } +} diff --git a/Dota2GSI/StateHandlers/ItemsHandler.cs b/Dota2GSI/StateHandlers/ItemsHandler.cs new file mode 100644 index 0000000..caf42dd --- /dev/null +++ b/Dota2GSI/StateHandlers/ItemsHandler.cs @@ -0,0 +1,145 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes.Helpers; +using System.Collections.Generic; + +namespace Dota2GSI +{ + public class ItemsHandler : EventHandler + { + private Dictionary _player_cache = new Dictionary(); + + public ItemsHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnItemsUpdated); + dispatcher.Subscribe(OnItemDetailsChanged); + } + + ~ItemsHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnItemsUpdated); + dispatcher.Unsubscribe(OnItemDetailsChanged); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + + _player_cache[evt.New.PlayerID] = evt.New; + } + + private void OnItemsUpdated(DotaGameEvent e) + { + ItemsUpdated evt = (e as ItemsUpdated); + + if (evt == null) + { + return; + } + + var local_player_details = _player_cache.GetValueOrDefault(-1, null); + + if (!evt.New.LocalPlayer.Equals(evt.Previous.LocalPlayer) && local_player_details != null) + { + dispatcher.Broadcast(new ItemDetailsChanged(evt.New.LocalPlayer, evt.Previous.LocalPlayer, local_player_details)); + } + + foreach (var team_kvp in evt.New.Teams) + { + foreach (var player_kvp in team_kvp.Value) + { + // Get corresponding previous hero details. + var player_details = _player_cache.GetValueOrDefault(player_kvp.Key, null); + var previous_hero_details = evt.Previous.GetForPlayer(player_kvp.Key); + + if (!player_kvp.Value.Equals(previous_hero_details) && player_details != null) + { + dispatcher.Broadcast(new ItemDetailsChanged(player_kvp.Value, previous_hero_details, player_details)); + } + } + } + } + + private void OnItemDetailsChanged(DotaGameEvent e) + { + ItemDetailsChanged evt = (e as ItemDetailsChanged); + + if (evt == null) + { + return; + } + + foreach (var item in evt.New.Inventory) + { + if (!evt.Previous.InventoryContains(item.Name)) + { + // Player gained an inventory item. + dispatcher.Broadcast(new InventoryItemAdded(item, evt.Player)); + continue; + } + + var previous_item = evt.Previous.GetInventoryItem(item.Name); + + if (!item.Equals(previous_item)) + { + // Item updated. + dispatcher.Broadcast(new InventoryItemUpdated(item, previous_item, evt.Player)); + } + } + + foreach (var item in evt.Previous.Inventory) + { + if (!evt.New.InventoryContains(item.Name)) + { + // Player lost an inventory item. + dispatcher.Broadcast(new InventoryItemRemoved(item, evt.Player)); + } + } + + foreach (var item in evt.New.Stash) + { + if (!evt.Previous.StashContains(item.Name)) + { + // Player gained a stash item. + dispatcher.Broadcast(new StashItemAdded(item, evt.Player)); + continue; + } + + var previous_item = evt.Previous.GetStashItem(item.Name); + + if (!item.Equals(previous_item)) + { + // Stash item updated. + dispatcher.Broadcast(new StashItemUpdated(item, previous_item, evt.Player)); + } + } + + foreach (var item in evt.Previous.Stash) + { + if (!evt.New.StashContains(item.Name)) + { + // Player lost a stash item. + dispatcher.Broadcast(new StashItemRemoved(item, evt.Player)); + } + } + + if (!evt.New.Teleport.Equals(evt.Previous.Teleport)) + { + // Teleport item updated. + dispatcher.Broadcast(new ItemUpdated(evt.New.Teleport, evt.Previous.Teleport, evt.Player)); + } + + if (!evt.New.Neutral.Equals(evt.Previous.Neutral)) + { + // Neutral item updated. + dispatcher.Broadcast(new ItemUpdated(evt.New.Neutral, evt.Previous.Neutral, evt.Player)); + } + } + } +} diff --git a/Dota2GSI/StateHandlers/LeagueHandler.cs b/Dota2GSI/StateHandlers/LeagueHandler.cs new file mode 100644 index 0000000..a2dd44e --- /dev/null +++ b/Dota2GSI/StateHandlers/LeagueHandler.cs @@ -0,0 +1,27 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class LeagueHandler : EventHandler + { + public LeagueHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnLeagueUpdated); + } + + ~LeagueHandler() + { + dispatcher.Unsubscribe(OnLeagueUpdated); + } + + private void OnLeagueUpdated(DotaGameEvent e) + { + LeagueUpdated evt = (e as LeagueUpdated); + + if (evt == null) + { + return; + } + } + } +} diff --git a/Dota2GSI/StateHandlers/MapHandler.cs b/Dota2GSI/StateHandlers/MapHandler.cs new file mode 100644 index 0000000..b458291 --- /dev/null +++ b/Dota2GSI/StateHandlers/MapHandler.cs @@ -0,0 +1,81 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class MapHandler : EventHandler + { + public MapHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnMapUpdated); + } + + ~MapHandler() + { + dispatcher.Unsubscribe(OnMapUpdated); + } + + private void OnMapUpdated(DotaGameEvent e) + { + MapUpdated evt = (e as MapUpdated); + + if (evt == null) + { + return; + } + + if (!evt.New.IsDaytime.Equals(evt.Previous.IsDaytime) || !evt.New.IsNightstalkerNight.Equals(evt.Previous.IsNightstalkerNight)) + { + dispatcher.Broadcast(new TimeOfDayChanged(evt.New.IsDaytime, evt.New.IsNightstalkerNight)); + } + + if (!evt.New.RadiantScore.Equals(evt.Previous.RadiantScore)) + { + dispatcher.Broadcast(new TeamScoreChanged(evt.New.RadiantScore, evt.Previous.RadiantScore, Nodes.PlayerTeam.Radiant)); + } + + if (!evt.New.DireScore.Equals(evt.Previous.DireScore)) + { + dispatcher.Broadcast(new TeamScoreChanged(evt.New.DireScore, evt.Previous.DireScore, Nodes.PlayerTeam.Dire)); + } + + if (!evt.New.GameState.Equals(evt.Previous.GameState)) + { + dispatcher.Broadcast(new GameStateChanged(evt.New.GameState, evt.Previous.GameState)); + } + + if (!evt.New.IsPaused.Equals(evt.Previous.IsPaused)) + { + dispatcher.Broadcast(new PauseStateChanged(evt.New.IsPaused, evt.Previous.IsPaused)); + + if (evt.New.IsPaused) + { + dispatcher.Broadcast(new GamePaused()); + } + else + { + dispatcher.Broadcast(new GameResumed()); + } + } + + if (!evt.New.WinningTeam.Equals(evt.Previous.WinningTeam) && evt.New.WinningTeam != Nodes.PlayerTeam.None && evt.New.WinningTeam != Nodes.PlayerTeam.Undefined) + { + dispatcher.Broadcast(new TeamVictory(evt.New.WinningTeam)); + + switch (evt.New.WinningTeam) + { + case Nodes.PlayerTeam.Radiant: + dispatcher.Broadcast(new TeamDefeat(Nodes.PlayerTeam.Dire)); + break; + case Nodes.PlayerTeam.Dire: + dispatcher.Broadcast(new TeamDefeat(Nodes.PlayerTeam.Radiant)); + break; + } + } + + if (!evt.New.RoshanState.Equals(evt.Previous.RoshanState)) + { + dispatcher.Broadcast(new RoshanStateChanged(evt.New.RoshanState, evt.Previous.RoshanState)); + } + } + } +} diff --git a/Dota2GSI/StateHandlers/MinimapHandler.cs b/Dota2GSI/StateHandlers/MinimapHandler.cs new file mode 100644 index 0000000..6b13a3d --- /dev/null +++ b/Dota2GSI/StateHandlers/MinimapHandler.cs @@ -0,0 +1,58 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class MinimapHandler : EventHandler + { + public MinimapHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnMinimapUpdated); + } + + ~MinimapHandler() + { + dispatcher.Unsubscribe(OnMinimapUpdated); + } + + private void OnMinimapUpdated(DotaGameEvent e) + { + MinimapUpdated evt = (e as MinimapUpdated); + + if (evt == null) + { + return; + } + + foreach (var element_kvp in evt.New.Elements) + { + if (!evt.Previous.Elements.ContainsKey(element_kvp.Key)) + { + // The element did not exist before. + dispatcher.Broadcast(new MinimapElementAdded(element_kvp.Value, element_kvp.Key.ToString())); + continue; + } + + var previous_element = evt.Previous.Elements[element_kvp.Key]; + + if (!element_kvp.Value.Equals(previous_element)) + { + dispatcher.Broadcast(new MinimapElementUpdated(element_kvp.Value, previous_element, element_kvp.Key.ToString())); + + if (element_kvp.Value.Team != Nodes.PlayerTeam.Undefined && element_kvp.Value.Team != Nodes.PlayerTeam.Unknown) + { + dispatcher.Broadcast(new TeamMinimapElementUpdated(element_kvp.Value, previous_element, element_kvp.Value.Team)); + } + } + } + + foreach (var element_kvp in evt.Previous.Elements) + { + if (!evt.New.Elements.ContainsKey(element_kvp.Key)) + { + // The element does not exist anymore. + dispatcher.Broadcast(new MinimapElementRemoved(element_kvp.Value, element_kvp.Key.ToString())); + } + } + } + } +} diff --git a/Dota2GSI/StateHandlers/NeutralItemsHandler.cs b/Dota2GSI/StateHandlers/NeutralItemsHandler.cs new file mode 100644 index 0000000..bdaca88 --- /dev/null +++ b/Dota2GSI/StateHandlers/NeutralItemsHandler.cs @@ -0,0 +1,42 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class NeutralItemsHandler : EventHandler + { + public NeutralItemsHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnNeutralItemsUpdated); + } + + ~NeutralItemsHandler() + { + dispatcher.Unsubscribe(OnNeutralItemsUpdated); + } + + private void OnNeutralItemsUpdated(DotaGameEvent e) + { + NeutralItemsUpdated evt = (e as NeutralItemsUpdated); + + if (evt == null) + { + return; + } + + foreach (var neutral_items_kvp in evt.New.TeamItems) + { + if (!evt.Previous.TeamItems.ContainsKey(neutral_items_kvp.Key)) + { + continue; + } + + var previous_neutral_items = evt.Previous.TeamItems[neutral_items_kvp.Key]; + + if (!neutral_items_kvp.Value.Equals(previous_neutral_items)) + { + dispatcher.Broadcast(new TeamNeutralItemsUpdated(neutral_items_kvp.Value, previous_neutral_items, neutral_items_kvp.Key)); + } + } + } + } +} diff --git a/Dota2GSI/StateHandlers/PlayerHandler.cs b/Dota2GSI/StateHandlers/PlayerHandler.cs new file mode 100644 index 0000000..5ea0d3e --- /dev/null +++ b/Dota2GSI/StateHandlers/PlayerHandler.cs @@ -0,0 +1,144 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes.Helpers; +using System.Collections.Generic; + +namespace Dota2GSI +{ + public class PlayerHandler : EventHandler + { + private Dictionary _player_cache = new Dictionary(); + + public PlayerHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnPlayerUpdated); + dispatcher.Subscribe(OnPlayerDetailsChanged); + } + + ~PlayerHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnPlayerUpdated); + dispatcher.Unsubscribe(OnPlayerDetailsChanged); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + + _player_cache[evt.New.PlayerID] = evt.New; + } + + private void OnPlayerUpdated(DotaGameEvent e) + { + PlayerUpdated evt = (e as PlayerUpdated); + + if (evt == null) + { + return; + } + + var local_player_details = _player_cache.GetValueOrDefault(-1, null); + + if (!evt.New.LocalPlayer.Equals(evt.Previous.LocalPlayer) && local_player_details != null) + { + dispatcher.Broadcast(new PlayerDetailsChanged(evt.New.LocalPlayer, evt.Previous.LocalPlayer, local_player_details)); + } + + foreach (var team_kvp in evt.New.Teams) + { + foreach (var player_kvp in team_kvp.Value) + { + var previous_hero_details = evt.Previous.GetForPlayer(player_kvp.Key); + + var player_details = _player_cache.GetValueOrDefault(player_kvp.Key, null); + + if (!player_kvp.Value.Equals(previous_hero_details) && player_details != null) + { + dispatcher.Broadcast(new PlayerDetailsChanged(player_kvp.Value, previous_hero_details, player_details)); + } + } + } + } + + private void OnPlayerDetailsChanged(DotaGameEvent e) + { + PlayerDetailsChanged evt = (e as PlayerDetailsChanged); + + if (evt == null) + { + return; + } + + if (!evt.New.Kills.Equals(evt.Previous.Kills)) + { + dispatcher.Broadcast(new PlayerKillsChanged(evt.New.Kills, evt.Previous.Kills, evt.Player)); + } + + if (!evt.New.Deaths.Equals(evt.Previous.Deaths)) + { + dispatcher.Broadcast(new PlayerDeathsChanged(evt.New.Deaths, evt.Previous.Deaths, evt.Player)); + } + + if (!evt.New.Assists.Equals(evt.Previous.Assists)) + { + dispatcher.Broadcast(new PlayerAssistsChanged(evt.New.Assists, evt.Previous.Assists, evt.Player)); + } + + if (!evt.New.LastHits.Equals(evt.Previous.LastHits)) + { + dispatcher.Broadcast(new PlayerLastHitsChanged(evt.New.LastHits, evt.Previous.LastHits, evt.Player)); + } + + if (!evt.New.Denies.Equals(evt.Previous.Denies)) + { + dispatcher.Broadcast(new PlayerDeniesChanged(evt.New.Denies, evt.Previous.Denies, evt.Player)); + } + + if (!evt.New.KillStreak.Equals(evt.Previous.KillStreak)) + { + dispatcher.Broadcast(new PlayerKillStreakChanged(evt.New.KillStreak, evt.Previous.KillStreak, evt.Player)); + } + + if (!evt.New.Gold.Equals(evt.Previous.Gold)) + { + dispatcher.Broadcast(new PlayerGoldChanged(evt.New.Gold, evt.Previous.Gold, evt.Player)); + } + + if (!evt.New.Gold.Equals(evt.Previous.Gold)) + { + dispatcher.Broadcast(new PlayerGoldChanged(evt.New.Gold, evt.Previous.Gold, evt.Player)); + } + + if (!evt.New.WardsPurchased.Equals(evt.Previous.WardsPurchased)) + { + dispatcher.Broadcast(new PlayerWardsPurchasedChanged(evt.New.WardsPurchased, evt.Previous.WardsPurchased, evt.Player)); + } + + if (!evt.New.WardsPlaced.Equals(evt.Previous.WardsPlaced)) + { + dispatcher.Broadcast(new PlayerWardsPlacedChanged(evt.New.WardsPlaced, evt.Previous.WardsPlaced, evt.Player)); + } + + if (!evt.New.WardsDestroyed.Equals(evt.Previous.WardsDestroyed)) + { + dispatcher.Broadcast(new PlayerWardsDestroyedChanged(evt.New.WardsDestroyed, evt.Previous.WardsDestroyed, evt.Player)); + } + + if (!evt.New.RunesActivated.Equals(evt.Previous.RunesActivated)) + { + dispatcher.Broadcast(new PlayerRunesActivatedChanged(evt.New.RunesActivated, evt.Previous.RunesActivated, evt.Player)); + } + + if (!evt.New.CampsStacked.Equals(evt.Previous.CampsStacked)) + { + dispatcher.Broadcast(new PlayerCampsStackedChanged(evt.New.CampsStacked, evt.Previous.CampsStacked, evt.Player)); + } + } + } +} diff --git a/Dota2GSI/StateHandlers/ProviderHandler.cs b/Dota2GSI/StateHandlers/ProviderHandler.cs new file mode 100644 index 0000000..3108a8b --- /dev/null +++ b/Dota2GSI/StateHandlers/ProviderHandler.cs @@ -0,0 +1,27 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class ProviderHandler : EventHandler + { + public ProviderHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnProviderUpdated); + } + + ~ProviderHandler() + { + dispatcher.Unsubscribe(OnProviderUpdated); + } + + private void OnProviderUpdated(DotaGameEvent e) + { + ProviderUpdated evt = (e as ProviderUpdated); + + if (evt == null) + { + return; + } + } + } +} diff --git a/Dota2GSI/StateHandlers/RoshanHandler.cs b/Dota2GSI/StateHandlers/RoshanHandler.cs new file mode 100644 index 0000000..c878a80 --- /dev/null +++ b/Dota2GSI/StateHandlers/RoshanHandler.cs @@ -0,0 +1,27 @@ +using Dota2GSI.EventMessages; + +namespace Dota2GSI +{ + public class RoshanHandler : EventHandler + { + public RoshanHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnRoshanUpdated); + } + + ~RoshanHandler() + { + dispatcher.Unsubscribe(OnRoshanUpdated); + } + + private void OnRoshanUpdated(DotaGameEvent e) + { + RoshanUpdated evt = (e as RoshanUpdated); + + if (evt == null) + { + return; + } + } + } +} diff --git a/Dota2GSI/StateHandlers/WearablesHandler.cs b/Dota2GSI/StateHandlers/WearablesHandler.cs new file mode 100644 index 0000000..785b863 --- /dev/null +++ b/Dota2GSI/StateHandlers/WearablesHandler.cs @@ -0,0 +1,66 @@ +using Dota2GSI.EventMessages; +using Dota2GSI.Nodes.Helpers; +using System.Collections.Generic; + +namespace Dota2GSI +{ + public class WearablesHandler : EventHandler + { + private Dictionary _player_cache = new Dictionary(); + + public WearablesHandler(ref EventDispatcher EventDispatcher) : base(ref EventDispatcher) + { + dispatcher.Subscribe(OnFullPlayerDetailsUpdated); + dispatcher.Subscribe(OnWearablesUpdated); + } + + ~WearablesHandler() + { + dispatcher.Unsubscribe(OnFullPlayerDetailsUpdated); + dispatcher.Unsubscribe(OnWearablesUpdated); + } + + private void OnFullPlayerDetailsUpdated(DotaGameEvent e) + { + FullPlayerDetailsUpdated evt = (e as FullPlayerDetailsUpdated); + + if (evt == null) + { + return; + } + + _player_cache[evt.New.PlayerID] = evt.New; + } + + private void OnWearablesUpdated(DotaGameEvent e) + { + WearablesUpdated evt = (e as WearablesUpdated); + + if (evt == null) + { + return; + } + + var local_player_details = _player_cache.GetValueOrDefault(-1, null); + + if (!evt.New.LocalPlayer.Equals(evt.Previous.LocalPlayer) && local_player_details != null) + { + dispatcher.Broadcast(new PlayerWearablesUpdated(evt.New.LocalPlayer, evt.Previous.LocalPlayer, local_player_details)); + } + + foreach (var team_kvp in evt.New.Teams) + { + foreach (var player_kvp in team_kvp.Value) + { + var player_details = _player_cache.GetValueOrDefault(player_kvp.Key, null); + var previous_hero_details = evt.Previous.GetForPlayer(player_kvp.Key); + + if (!player_kvp.Value.Equals(previous_hero_details) && player_details != null) + { + dispatcher.Broadcast(new PlayerWearablesUpdated(player_kvp.Value, previous_hero_details, player_details)); + } + } + } + } + } +} diff --git a/README.md b/README.md index a5e3dce..5dbe43a 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,271 @@ -# Dota 2 GSI (Game State Integration) -A C# library to interface with the Game State Integration found in Dota 2. +Dota 2 GSI (Game State Integration) +=================================== -## What is Game State Integration -You can read about Game State Integration for Counter-Strike: Global Offensive [here](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration). +[![.NET](https://github.com/antonpup/Dota2GSI/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/antonpup/Dota2GSI/actions/workflows/dotnet.yml) +![GitHub Release](https://img.shields.io/github/v/release/antonpup/Dota2GSI) +[![NuGet Version](https://img.shields.io/nuget/v/Dota2GSI)](https://www.nuget.org/packages/Dota2GSI) + +A C# library to interface with the Game State Integration found in Dota 2. ## 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). +This library provides an easy way to implement Game State Integration from Dota 2 into C# applications through exposing a number of events. + +Underneath the hood, once the library is started, it continuously listens for HTTP POST requests made by the game on a specific address and port. When a request is received, the JSON data is parsed into [GameState object](#game-state-structure) and is offered to your C# application through `NewGameState` event. The library also subscribes to `NewGameState` to determine more granular changes to raise more specific events _(such as `TimeOfDayChanged`, `InventoryItemAdded`, or `TowerDestroyed` to name a few)_. A full list of exposed Game Events can be found in the [Implemented Game Events](#implemented-game-events) section. + +## About Game State Integration -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. +Game State Integration is Valve's implementation for exposing current game state _(such as player health, mana, ammo, etc.)_ and game events without the need to read game memory or risking anti-cheat detection. The information exposed by GSI is limited to what Valve has determined to expose. For example, the game can expose information about all players in the game while spectating a match, but will only expose local player's infomration when playing a game. While the information is limited, there is enough information to create a live game analysis tool, create custom RGB ligthing effects, or create live streaming plugin to show additional game information. For example, GSI can be seen used during competitive tournament live streams to show currently spectated player's information and in-game statistics. + +You can read about Game State Integration for Counter-Strike: Global Offensive [here](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration). ## Installation + Install from [nuget](https://www.nuget.org/packages/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\Net80\Newtonsoft.Json.dll` -4. Add a reference to both Dota2GSI.dll and Newtonsoft.Json.dll in your project +## Building Dota 2 GSI -## Build Dota 2 GSI 1. Make sure you have Visual Studio installed with `.NET desktop development` workload and `.Net 8.0 Runtime` individual component. 2. Make sure you have CMake 3.26 or later installed from [https://cmake.org/](https://cmake.org/). 3. In the repository root directory run: `cmake -B build/ .` to generate the project solution file. 4. Open the project solution located in `build/Dota2GSI.sln`. -## Usage -1. Create a `GameStateListener` instance by providing a port or passing a specific URI: +## How to use +1. Before `Dota2GSI` can be used, you must create a Game State Integration configuration file where you would specify the URI for the HTTP Requests and select which providers the game should expose to your application. To do this you must create a new configuration file in `/game/dota/cfg/gamestate_integration/gamestate_integration_.cfg` where `` should be the name of your application (it can be anything). Add the following content to your configuration file (make sure you replace `` with your application's name, `` with the port you would like to use for your application, and other values can also be tweaked as you wish): +``` +" Integration Configuration" +{ + "uri" "http://localhost:/" + "timeout" "5.0" + "buffer" "0.1" + "throttle" "0.1" + "heartbeat" "30.0" + "data" + { + "auth" "1" + "provider" "1" + "map" "1" + "player" "1" + "hero" "1" + "abilities" "1" + "items" "1" + "events" "1" + "buildings" "1" + "league" "1" + "draft" "1" + "wearables" "1" + "minimap" "1" + "roshan" "1" + "couriers" "1" + "neutralitems" "1" + } +} +``` + +2. After installing the [Dota2GSI nuget package](https://www.nuget.org/packages/Dota2GSI) in your project, create an instance of `GameStateListener`, providing a custom port or custom URI you defined in the configuration file in the previous step. ```C# GameStateListener gsl = new GameStateListener(3000); //http://localhost:3000/ -GameStateListener gsl = new GameStateListener("http://127.0.0.1:81/"); ``` +or +```C# +GameStateListener gsl = new GameStateListener("http://127.0.0.1:1234/"); +``` +> **Please note**: If your application needs to listen to a URI other than `http://localhost:*/` (for example `http://192.168.0.2:100/`), you will need to run your application with administrator privileges. -**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: - +3. Create handlers and subscribe for events your application will be using. (A full list of exposed Game Events can be found in the [Implemented Game Events](#implemented-game-events) section.) +If your application just needs `GameState` information, this is done by subscribing to `NewGameState` event and creating a handler for it: ```C# +... +gsl.NewGameState += OnNewGameState; +... + void OnNewGameState(GameState gs) { - //do stuff + // Read information from the game state. } ``` +If you would like to utilize `Game Events` in your application, this is done by subscribing to an event from the [Implemented Game Events](#implemented-game-events) list and creating a handler for it: +```C# +... +gsl.GameEvent += OnGameEvent; // Will fire on every GameEvent +gsl.TimeOfDayChanged += OnTimeOfDayChanged; // Will only fire on TimeOfDayChanged events. +gsl.InventoryItemAdded += OnInventoryItemAdded; // Will only fire on InventoryItemAdded events. +... -3. Subscribe to the `NewGameState` event: +void OnGameEvent(DotaGameEvent game_event) +{ + // Read information from the game event. + + if (game_event is TimeOfDayChanged tod_changed) + { + Console.WriteLine($"Is daytime: {tod_changed.IsDaytime} Is Nightstalker night: {tod_changed.IsNightstalkerNight}"); + } +} + +void OnTimeOfDayChanged(TimeOfDayChanged game_event) +{ + Console.WriteLine($"Is daytime: {game_event.IsDaytime} Is Nightstalker night: {game_event.IsNightstalkerNight}"); +} +void OnInventoryItemAdded(InventoryItemAdded game_event) +{ + Console.WriteLine($"Player {game_event.Player.Details.Name} gained an item in their inventory: {game_event.Value.Name}"); +} +``` +Both `NewGameState` and `Game Events` can be used alongside one another. The `Game Events` are generated based on the `GameState`, and are there to provide ease of use. + +4. Finally you want to start the `GameStateListener` to begin capturing HTTP POST requests. This is done by calling `Start()` method of `GameStateListener`. The method will return `True` if started successfully, or `False` when failed to start. Often the failure to start is due to insufficient permissions or another application is already using the same port. ```C# -gsl.NewGameState += new NewGameStateHandler(OnNewGameState); +if (!gsl.Start()) +{ + // GameStateListener could not start. +} +// GameStateListener started and is listening for Game State requests. ``` -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). +## Implemented Game Events + +* `GameEvent` The base game event, will fire for all other listed events. + +### Abilities Events + +* `AbilitiesUpdated` +* `AbilityDetailsChanged` +* `AbilityAdded` +* `AbilityRemoved` +* `AbilityUpdated` + +### Auth Events + +* `AuthUpdated` + +### Buildings Events + +* `BuildingsUpdated` +* `BuildingsLayoutUpdated` +* `BuildingUpdated` +* `TeamBuildingUpdated` +* `TeamBuildingDestroyed` +* `TowerUpdated` +* `TowerDestroyed` +* `RacksUpdated` +* `RacksDestroyed` +* `AncientUpdated` +* `AncientDestroyed` + +### Couriers Events + +* `CouriersUpdated` +* `CourierUpdated` +* `TeamCourierUpdated` +* `CourierItemAdded` +* `CourierItemRemoved` +* `CourierItemUpdated` + +### Draft Events + +* `DraftUpdated` +* `TeamDraftDetailsUpdated` + +### Gameplay Events + +* `EventsUpdated` +* `GameplayEvent` +* `TeamGameplayEvent` +* `PlayerGameplayEvent` + +### Hero Events + +* `HeroUpdated` +* `HeroDetailsChanged` +* `HeroLevelChanged` +* `HeroHealthChanged` +* `HeroDied` +* `HeroRespawned` +* `HeroTookDamage` +* `HeroManaChanged` +* `HeroStateChanged` +* `HeroMuteStateChanged` +* `HeroSelectedChanged` +* `HeroTalentTreeChanged` +* `HeroAttributesLevelChanged` + +### Items Events + +* `ItemsUpdated` +* `ItemDetailsChanged` +* `ItemUpdated` +* `InventoryItemAdded` +* `InventoryItemRemoved` +* `InventoryItemUpdated` +* `StashItemAdded` +* `StashItemRemoved` +* `StashItemUpdated` + +### League Events + +* `LeagueUpdated` + +### Map Events + +* `MapUpdated` +* `TimeOfDayChanged` +* `TeamScoreChanged` +* `GameStateChanged` +* `PauseStateChanged` +* `GamePaused` +* `GameResumed` +* `TeamVictory` +* `TeamDefeat` +* `RoshanStateChanged` + +### Minimap Events + +* `MinimapUpdated` +* `MinimapElementAdded` +* `MinimapElementUpdated` +* `MinimapElementRemoved` +* `TeamMinimapElementUpdated` + +### NeutralItems Events + +* `NeutralItemsUpdated` +* `TeamNeutralItemsUpdated` + +### Player Events + +* `PlayerUpdated` +* `PlayerDetailsChanged` +* `PlayerKillsChanged` +* `PlayerDeathsChanged` +* `PlayerAssistsChanged` +* `PlayerLastHitsChanged` +* `PlayerDeniesChanged` +* `PlayerKillStreakChanged` +* `PlayerGoldChanged` +* `PlayerWardsPurchasedChanged` +* `PlayerWardsPlacedChanged` +* `PlayerWardsDestroyedChanged` +* `PlayerRunesActivatedChanged` +* `PlayerCampsStackedChanged` + +### Provider Events + +* `ProviderUpdated` + +### Roshan Events + +* `RoshanUpdated` + +### Wearables Events + +* `RoshanUpdated` +* `WearablesUpdated` +* `PlayerWearablesUpdated` + +## Game State Structure -## Layout ``` GameState +-- Auth @@ -77,11 +288,11 @@ GameState | +-- IsPaused | +-- Winningteam | +-- CustomGameName +| +-- WardPurchaseCooldown | +-- RadiantWardPurchaseCooldown | +-- DireWardPurchaseCooldown | +-- RoshanState | +-- RoshanStateEndTime -| +-- WardPurchaseCooldown +-- Player | +-- LocalPlayer | | +-- SteamID @@ -96,6 +307,9 @@ GameState | | +-- KillStreak | | +-- CommandsIssued | | +-- KillList +| | | \ +| | | (Map of kill id to player id) +| | | ... | | +-- Team | | +-- PlayerSlot | | +-- PlayerTeamSlot @@ -113,17 +327,19 @@ GameState | | +-- HeroDamage | | +-- HeroHealing | | +-- TowerDamage -| | +-- WardsPurchased -| | +-- WardsPlaced -| | +-- WardsDestroyed -| | +-- RunesActivated -| | +-- CampsStacked | | +-- SupportGoldSpent | | +-- ConsumableGoldSpent | | +-- ItemGoldSpent | | +-- GoldLostToDeath | | +-- GoldSpentOnBuybacks +| | +-- WardsPurchased +| | +-- WardsPlaced +| | +-- WardsDestroyed +| | +-- RunesActivated +| | +-- CampsStacked | +-- Teams +| | \ +| | (Multi-map of team to player id to Player Details) | | ... | +-- GetForTeam( team ) | +-- GetForPlayer( player_id ) @@ -144,21 +360,24 @@ GameState | | +-- Mana | | +-- MaxMana | | +-- ManaPercent +| | +-- HeroState | | +-- IsSilenced | | +-- IsStunned | | +-- IsDisarmed | | +-- IsMagicImmune | | +-- IsHexed -| | +-- IsMuted | | +-- IsBreak -| | +-- HasAghanimsScepterUpgrade -| | +-- HasAghanimsShardUpgrade | | +-- IsSmoked | | +-- HasDebuff +| | +-- IsMuted +| | +-- HasAghanimsScepterUpgrade +| | +-- HasAghanimsShardUpgrade | | +-- SelectedUnit | | +-- TalentTree[] | | +-- AttributesLevel | +-- Teams +| | \ +| | (Multi-map of team to player id to Hero Details) | | ... | +-- GetForTeam( team ) | +-- GetForPlayer( player_id ) @@ -178,17 +397,35 @@ GameState | | | +-- MaxCharges | | | +-- ChargeCooldown | +-- Teams +| | \ +| | (Multi-map of team to player id to Ability Details) | | ... | +-- GetForTeam( team ) | +-- GetForPlayer( player_id ) +-- Items | +-- LocalPlayer -| | +-- Inventory -| | +-- Stash +| | +-- Inventory[] +| | | \ +| | | +-- Name +| | | +-- Purchaser +| | | +-- ItemLevel +| | | +-- ContainsRune +| | | +-- CanCast +| | | +-- Cooldown +| | | +-- IsPassive +| | | +-- ItemCharges +| | | +-- AbilityCharges +| | | +-- MaxCharges +| | | +-- ChargeCooldown +| | | +-- Charges +| | +-- Stash[] +| | | ... | | +-- CountInventory | | +-- CountStash | | +-- Teleport +| | | ... | | +-- Neutral +| | | ... | | +-- GetInventoryAt( index ) | | +-- GetInventoryItem( item_name ) | | +-- InventoryContains( item_name ) @@ -198,6 +435,8 @@ GameState | | +-- StashContains( item_name ) | | +-- StashIndexOf( item_name ) | +-- Teams +| | \ +| | (Multi-map of team to player id to Item Details) | | ... | +-- GetForTeam( team ) | +-- GetForPlayer( player_id ) @@ -218,21 +457,52 @@ GameState +-- Buildings | +-- RadiantBuildings | | +-- TopTowers +| | | \ +| | | (Map of tower id to Building) +| | | +-- Health +| | | +-- MaxHealth | | +-- MiddleTowers +| | | \ +| | | (Map of tower id to Building) +| | | ... | | +-- BottomTowers +| | | \ +| | | (Map of tower id to Building) +| | | ... | | +-- TopRacks +| | | \ +| | | (Map of Racks Type to Building) +| | | ... | | +-- MiddleRacks +| | | \ +| | | (Map of Racks Type to Building) +| | | ... | | +-- BottomRacks +| | | \ +| | | (Map of Racks Type to Building) +| | | ... | | +-- Ancient +| | | ... | | +-- OtherBuildings +| | | \ +| | | (Map of building id to Building) +| | | ... | +-- DireBuildings | | ... | +-- AllBuildings +| | \ +| | (Map of team to Building Layout) | | ... | +-- GetForTeam( team ) +-- League | +-- SeriesType | +-- SelectionPriority +| | +-- Rules +| | +-- PreviousPriorityTeamID +| | +-- CurrentPriorityTeamID +| | +-- PriorityTeamChoice +| | +-- NonPriorityTeamChoice +| | +-- UsedCoinToss | +-- LeagueID | +-- MatchID | +-- Name @@ -262,7 +532,7 @@ GameState | +-- StartTime | +-- FirstTeamID | +-- SecondTeamID -| +-- Stream[] +| +-- Streams[] | | \ | | +-- StreamID | | +-- Language @@ -277,21 +547,33 @@ GameState | +-- RadiantBonusTime | +-- DireBonusTime | +-- Teams +| | | \ +| | | (Map of team to Draft Details) | | +-- IsHomeTeam | | +-- PickIDs +| | | \ +| | | (Map of slot number to picked hero ID) | | +-- PickHeroIDs +| | | \ +| | | (Map of slot number to picked hero name) | +-- GetForTeam( team ) +-- Wearables | +-- LocalPlayer | | +-- Wearables +| | | \ +| | | (Map of slot number to Wearable Item) | | | +-- ID | | | +-- Style | +-- Teams +| | \ +| | (Multi-map of team to player id to Item Wearable Item) | | ... | +-- GetForTeam( team ) | +-- GetForPlayer( player_id ) +-- Minimap | +-- Elements +| | | \ +| | | (Map of element ID to Minimap Element) | | +-- Location | | +-- RemainingTime | | +-- EventDuration @@ -302,19 +584,23 @@ GameState | | +-- UnitName | | +-- VisionRange | +-- GetForTeam( team ) -| +-- GetForPlayer( player_id ) +| +-- GetByUnitName( unit_name ) +-- Roshan -| +-- Location | +-- Health | +-- MaxHealth | +-- IsAlive | +-- SpawnPhase | +-- PhaseTimeRemaining +| +-- Location | +-- Rotation | +-- Drops | | +-- Items +| | | \ +| | | (Map of item index to item name) +-- Couriers | +-- CouriersMap +| | \ +| | (Map of courier ID to Courier) | | +-- Health | | +-- MaxHealth | | +-- IsAlive @@ -326,17 +612,29 @@ GameState | | +-- IsShielded | | +-- IsBoosted | | +-- Items +| | | \ +| | | (Map of item index to Courier Item) | | | +-- OwnerID | | | +-- Name +| | +-- GetItemAt( index ) +| | +-- GetInventoryItem( item_name ) +| | +-- InventoryContains( item_name ) +| | +-- InventoryIndexOf( item_name ) | +-- GetForPlayer( player_id ) +-- NeutralItems | +-- TierInfos +| | | \ +| | | (Map of tier index to Neutral Tier Info) | | +-- Tier | | +-- MaxCount | | +-- DropAfterTime | +-- TeamItems +| | | \ +| | | (Map of team to Team Neutral Items) | | +-- ItemsFound | | +-- TeamItems +| | | \ +| | | (Map of item index to Neutral Item) | | | +-- Name | | | +-- Tier | | | +-- Charges @@ -345,6 +643,8 @@ GameState | +-- GetForTeam( team ) +-- Previously (Previous information from Game State) +-- LocalPlayer +| +-- PlayerID +| +-- IsLocalPlayer | +-- Details | +-- Hero | +-- Abilities @@ -369,119 +669,31 @@ GameState +-- IsLocalPlayer ``` -### Item, and Hero names -Item and hero names are presented in their "internal name" format. A full list of item names can be found [here](http://dota2.gamepedia.com/Cheats#Item_names) and a full list of heroes can be located [here](http://dota2.gamepedia.com/Cheats#Hero_names). - -##### Examples: -```C# -int Health = gs.Hero.LocalPlayer.Health; // 560 -int MaxHealth = gs.Hero.LocalPlayer.MaxHealth; // 560 -string HeroName = gs.Hero.LocalPlayer.Name; //npc_dota_hero_omniknight -int Level = gs.Hero.LocalPlayer.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 ---------|------------- -bool | false -int | -1 -long | -1 -float | -1 -string | String.Empty +In the event that the game state is missing information, a default value will be returned: -All Enums have a value `enum.Undefined` that serves the same purpose. +| Type | Default value | +|:---------|:---------------| +| `bool` | `False` | +| `int` | `-1` | +| `long` | `-1` | +| `float` | `-1` | +| `string` | `String.Empty` | +| `enum` | `Undefined` | ## Example program -A user, [judge2020](https://github.com/judge2020), has created an example program to demonstrate Dota2GSI functionalities. It can be found in the "Dota2GSI Example program" folder. - -## Example implementation -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.Previously.Items.LocalPlayer.CountInventory > gs.Items.LocalPlayer.CountInventory) - { - Console.WriteLine("You bought an item"); - } - - if(!gs.Map.IsDaytime || gs.Map.IsNightstalkerNight) - { - Console.WriteLine("It is night time"); - } - } - } - } -} -``` +An example program for `Dota2GSI` can be found in the `Dota2GSI Example program` directory in the root of the repository. It was initially created by [judge2020](https://github.com/judge2020). -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" - { - "auth" "1" - "provider" "1" - "map" "1" - "player" "1" - "hero" "1" - "abilities" "1" - "items" "1" - "events" "1" - "buildings" "1" - "league" "1" - "draft" "1" - "wearables" "1" - "minimap" "1" - "roshan" "1" - "couriers" "1" - "neutralitems" "1" - } -} +### Item and hero names -``` +A full list of item names can be found [here](https://dota2.fandom.com/wiki/Cheats#Item_names). -**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. +A full list of hero names can be found [here](https://dota2.fandom.com/wiki/Cheats#Hero_Names). ## Credits -Special thanks to [rakijah](https://github.com/rakijah) for his CSGO Game State Integration library. -Thanks to [judge2020](https://github.com/judge2020) for providing an example program. +Thank you to [rakijah](https://github.com/rakijah) for his work on the CSGO Game State Integration library. + +Thank you to [judge2020](https://github.com/judge2020) for providing an example program.