using System; using System.IO; using System.Linq; using System.Collections.Generic; using Hearthstone_Deck_Tracker.API; using Hearthstone_Deck_Tracker.Hearthstone; using Hearthstone_Deck_Tracker.Plugins; using HearthDb.Enums; using Hearthstone_Deck_Tracker.Enums; using System.Windows.Controls; namespace DeepBattlerPlugin { public class CardEntityInfo { public string Name { get; set; } public int? Atk { get; set; } public int? Health { get; set; } public string Description { get; set; } public string Race { get; set; } public bool IsSpell { get; set; } public int? Tier { get; set; } } public enum GamePhaseEnum { StartGame, PlayerTurn, OpponentTurn, EndGame } public class DeepBattlerPlugin : IPlugin { private static readonly int[] BASE_TAVERN_UPGRADE_COSTS = { 5, 7, 8, 10, 10 }; private int? _currentTavernUpgradeCost = null; public string Name => "DeepBattlerPlugin"; public string Description => "Track BG state with simplified logic"; public string ButtonText => "Do Nothing"; public string Author => "Guanming"; public Version Version => new Version(1, 0, 10); public MenuItem MenuItem => null; private bool _isPlayerTurnStarted = false; private int _updateCounterSincePlayerTurn = 0; private const int DelayBeforeFetchingTavern = 5; public GamePhaseEnum CurrentPhase { get; private set; } = GamePhaseEnum.StartGame; private int _lastHeroHealth; private int _lastCoins; private int _lastTavernTier = 1; private CardEntityInfo[] _lastWarband = Array.Empty(); private CardEntityInfo[] _lastTavernEntities = Array.Empty(); private CardEntityInfo[] _lastHand = Array.Empty(); private readonly string _path = @"C:\Users\Guanming Wang\Desktop\DeepBattler\Agent\game_state.json"; private string _heroName = "Unknown Hero"; private int _playerHeroId = 0; private bool _upgradedThisTurn = false; public void OnLoad() { GameEvents.OnGameStart.Add(OnGameStart); GameEvents.OnTurnStart.Add(OnTurnStart); GameEvents.OnGameEnd.Add(OnGameEnd); } public void OnUnload() { } public void OnButtonPress() { } private bool IsInRecruitmentPhase() { var playerEntity = Core.Game.PlayerEntity; if (playerEntity == null) return false; return Core.Game.Entities.Values.Any(e => e.GetTag(GameTag.IS_BACON_POOL_MINION) == 1); } public void OnUpdate() { if (_heroName == "Unknown Hero" || _heroName.StartsWith("BaconPHhero")) UpdateHeroName(); if (_isPlayerTurnStarted) { if (!IsInRecruitmentPhase()) return; _updateCounterSincePlayerTurn++; if (_updateCounterSincePlayerTurn >= DelayBeforeFetchingTavern) UpdateGameState(); } else { UpdateGameState(); } } private void OnGameStart() { var playerHero = Core.Game.Player?.Hero; if (playerHero != null) { var heroCardId = playerHero.CardId; if (heroCardId != null && HearthDb.Cards.All.TryGetValue(heroCardId, out var foundHeroCard)) _heroName = foundHeroCard?.Name ?? "Unknown Hero"; else _heroName = "Unknown Hero"; _playerHeroId = playerHero.Id; } _lastTavernTier = 1; CurrentPhase = GamePhaseEnum.StartGame; _currentTavernUpgradeCost = null; UpdateGameState(true); } private void OnGameEnd() { CurrentPhase = GamePhaseEnum.EndGame; UpdateGameState(); ResetInternalState(); } private void ResetInternalState() { _currentTavernUpgradeCost = null; _lastHeroHealth = 0; _lastCoins = 0; _lastTavernTier = 1; _lastWarband = Array.Empty(); _lastTavernEntities = Array.Empty(); _lastHand = Array.Empty(); _heroName = "Unknown Hero"; _playerHeroId = 0; _isPlayerTurnStarted = false; _upgradedThisTurn = false; } private void OnTurnStart(ActivePlayer player) { if (player == ActivePlayer.Player) { if (_currentTavernUpgradeCost == null) { _currentTavernUpgradeCost = BASE_TAVERN_UPGRADE_COSTS[0]; } else { _currentTavernUpgradeCost = Math.Max(0, _currentTavernUpgradeCost.Value - 1); } _upgradedThisTurn = false; CurrentPhase = GamePhaseEnum.PlayerTurn; _isPlayerTurnStarted = true; _updateCounterSincePlayerTurn = 0; UpdateGameState(); } var playerEntity = Core.Game.PlayerEntity; if (playerEntity == null) return; if (player == ActivePlayer.Opponent) { CurrentPhase = GamePhaseEnum.OpponentTurn; UpdateGameState(); } } public void PlayerUpgradedTavern(int newTier) { _upgradedThisTurn = true; _lastTavernTier = newTier; if (newTier < 6) { _currentTavernUpgradeCost = BASE_TAVERN_UPGRADE_COSTS[newTier - 1]; } else { _currentTavernUpgradeCost = null; } UpdateGameState(true); } private void UpdateHeroName() { var playerHero = Core.Game.Player?.Hero; if (playerHero != null) { var heroCardId = playerHero.CardId; if (heroCardId != null && HearthDb.Cards.All.TryGetValue(heroCardId, out var foundHeroCard)) { if (!string.IsNullOrEmpty(foundHeroCard.Name) && !foundHeroCard.Name.StartsWith("BaconPHhero")) { _heroName = foundHeroCard.Name; UpdateGameState(); } } } } private void UpdateGameState(bool forceWrite = false) { var heroEntity = Core.Game.Player?.Hero; var playerEntity = Core.Game.PlayerEntity; if (heroEntity == null || playerEntity == null) return; var playerId = playerEntity.GetTag(GameTag.PLAYER_ID); var heroPowerEntity = Core.Game.Entities.Values.FirstOrDefault(e => e.GetTag(GameTag.CONTROLLER) == playerId && e.GetTag(GameTag.CARDTYPE) == (int)CardType.HERO_POWER); var baseHealth = heroEntity.GetTag(GameTag.HEALTH); var damage = heroEntity.GetTag(GameTag.DAMAGE); var actualHealth = baseHealth - damage; var coins = playerEntity.GetTag(GameTag.RESOURCES); var coinsUsed = playerEntity.GetTag(GameTag.RESOURCES_USED); var availableCoins = coins - coinsUsed; var tavernTier = playerEntity.GetTag(GameTag.PLAYER_TECH_LEVEL); if (tavernTier > _lastTavernTier) { PlayerUpgradedTavern(tavernTier); return; } var currentWarband = ExtractWarband(); var currentHand = ExtractHand(); var currentTavernEntities = ExtractTavernEntitiesWithDetails(); var heroPowerDescription = CleanCardText(heroPowerEntity?.Card?.Text); var heroPowerCost = heroPowerEntity?.GetTag(GameTag.COST) ?? 0; var tavernUpgradeCostStr = "None"; if (CurrentPhase == GamePhaseEnum.StartGame) { tavernUpgradeCostStr = "undefined"; } else if (tavernTier < 6) { if (_currentTavernUpgradeCost == null && CurrentPhase == GamePhaseEnum.PlayerTurn) { _currentTavernUpgradeCost = BASE_TAVERN_UPGRADE_COSTS[0]; } if (_currentTavernUpgradeCost.HasValue) { tavernUpgradeCostStr = _currentTavernUpgradeCost.Value.ToString(); } } bool changed = forceWrite || actualHealth != _lastHeroHealth || availableCoins != _lastCoins || tavernTier != _lastTavernTier || !EntitiesEqual(currentWarband, _lastWarband) || !EntitiesEqual(currentTavernEntities, _lastTavernEntities) || !EntitiesEqual(currentHand, _lastHand); if (changed) { _lastHeroHealth = actualHealth; _lastCoins = availableCoins; _lastWarband = currentWarband; _lastTavernEntities = currentTavernEntities; _lastHand = currentHand; var json = "{"; json += $"\"HeroName\":\"{EscapeJson(_heroName)}\","; json += $"\"HeroHealth\":{actualHealth},"; json += $"\"Coins\":{availableCoins},"; json += $"\"TavernUpgradeCost\":\"{tavernUpgradeCostStr}\","; json += $"\"HeroPowerDescription\":\"{EscapeJson(heroPowerDescription)}\","; json += $"\"CurrentTavernTier\":{tavernTier},"; json += $"\"HeroPowerCost\":{heroPowerCost},"; json += "\"Warband\":" + EntitiesToJson(currentWarband) + ","; json += "\"Hand\":" + EntitiesToJsonNoRace(currentHand) + ","; json += "\"TavernEntities\":" + EntitiesToJson(currentTavernEntities); json += "}"; try { File.WriteAllText(_path, json); } catch (Exception) { } } } private CardEntityInfo[] ExtractWarband() { return (Core.Game.Player?.Board? .Where(e => e.GetTag(GameTag.ZONE) == (int)Zone.PLAY && e.GetTag(GameTag.CARDTYPE) == (int)CardType.MINION && e.Card != null) .Select(e => new CardEntityInfo { Name = e.Card.Name, Atk = e.GetTag(GameTag.ATK), Health = e.GetTag(GameTag.HEALTH) - e.GetTag(GameTag.DAMAGE), Description = CleanCardText(e.Card.Text), Race = GetRaceString(e.Card.Race), IsSpell = false, Tier = e.Card?.TechLevel }) .ToArray()) ?? Array.Empty(); } private CardEntityInfo[] ExtractHand() { return (Core.Game.Player?.Hand? .Where(e => e.Card != null && !string.IsNullOrEmpty(e.Card.Name) ) .Select(e => new CardEntityInfo { Name = e.Card.Name, Description = CleanCardText(e.Card.Text), IsSpell = e.GetTag(GameTag.CARDTYPE) == (int)CardType.BATTLEGROUND_SPELL }) .ToArray()) ?? Array.Empty(); } private CardEntityInfo[] ExtractTavernEntitiesWithDetails() { if (Core.Game.PlayerEntity == null) return Array.Empty(); var playerId = Core.Game.PlayerEntity.GetTag(GameTag.PLAYER_ID); var tavernMinions = Core.Game.Entities.Values .Where(e => e.GetTag(GameTag.ZONE) == (int)Zone.PLAY && e.Id != _playerHeroId && e.Card != null && !string.IsNullOrEmpty(e.Card.Name) && e.GetTag(GameTag.IS_BACON_POOL_MINION) == 1 && e.GetTag(GameTag.CONTROLLER) != playerId && e.GetTag(GameTag.CARDTYPE) == (int)CardType.MINION) .Select(e => new CardEntityInfo { Name = e.Card.Name, Atk = e.GetTag(GameTag.ATK), Health = e.GetTag(GameTag.HEALTH) - e.GetTag(GameTag.DAMAGE), Description = CleanCardText(e.Card.Text), Race = GetRaceString(e.Card.Race), IsSpell = false, Tier = e.Card?.TechLevel }); var tavernSpells = Core.Game.Entities.Values .Where(e => e.GetTag(GameTag.CARDTYPE) == (int)CardType.BATTLEGROUND_SPELL && e.GetTag(GameTag.ZONE) == (int)Zone.PLAY && e.Card != null && !string.IsNullOrEmpty(e.Card.Name)) .Select(e => new CardEntityInfo { Name = e.Card.Name, Description = CleanCardText(e.Card.Text), Race = "Tavern Spell", IsSpell = true, Tier = e.Card?.TechLevel }); return tavernMinions.Concat(tavernSpells).ToArray(); } private bool EntitiesEqual(CardEntityInfo[] arr1, CardEntityInfo[] arr2) { if (arr1.Length != arr2.Length) return false; for (int i = 0; i < arr1.Length; i++) { if (arr1[i].Name != arr2[i].Name || arr1[i].Description != arr2[i].Description || arr1[i].Race != arr2[i].Race || arr1[i].IsSpell != arr2[i].IsSpell || arr1[i].Tier != arr2[i].Tier) return false; if (!arr1[i].IsSpell && (arr1[i].Atk != arr2[i].Atk || arr1[i].Health != arr2[i].Health)) return false; } return true; } private string EntitiesToJson(CardEntityInfo[] entities) { var parts = new List(); foreach (var e in entities) { if (e.IsSpell) parts.Add(SpellToJson(e)); else parts.Add(MinionToJson(e)); } return "[" + string.Join(",", parts) + "]"; } private string MinionToJson(CardEntityInfo entity) { return "{" + $"\"Name\":\"{EscapeJson(entity.Name)}\"," + $"\"Atk\":{entity.Atk}," + $"\"Health\":{entity.Health}," + $"\"Description\":\"{EscapeJson(entity.Description)}\"," + $"\"Race\":\"{EscapeJson(entity.Race)}\"," + $"\"Tier\":{entity.Tier}" + "}"; } private string SpellToJson(CardEntityInfo entity) { return "{" + $"\"Name\":\"{EscapeJson(entity.Name)}\"," + $"\"Description\":\"{EscapeJson(entity.Description)}\"," + $"\"Race\":\"Tavern Spell\"," + $"\"Tier\":{entity.Tier}" + "}"; } private string EntitiesToJsonNoRace(CardEntityInfo[] entities) { var parts = new List(); foreach (var e in entities) { var part = "{"; part += $"\"Name\":\"{EscapeJson(e.Name)}\","; part += $"\"Description\":\"{EscapeJson(e.Description)}\""; part += "}"; parts.Add(part); } return "[" + string.Join(",", parts) + "]"; } private string EscapeJson(string value) { if (string.IsNullOrEmpty(value)) return ""; return value .Replace("\\", "\\\\") .Replace("\"", "\\\"") .Replace("\n", "\\n") .Replace("\r", "\\r") .Replace("\t", "\\t"); } private string CleanCardText(string text) { if (string.IsNullOrEmpty(text)) return ""; return text.Replace("\r", " ").Replace("\n", " ").Trim(); } private string GetRaceString(string race) { return string.IsNullOrEmpty(race) ? "" : race; } } }