public static List <Option> PythonOptions(this Controller c, int gameId) { if (c.Choice != null) { if (c.Choice.ChoiceType == ChoiceType.GENERAL) { return(c.Choice.Choices.Select(p => new Option(gameId, p, c.Game.IdEntityDic[p].Card.Name)).ToList()); } throw new NotImplementedException(); } int controllerId = c.Id; List <Option> allOptions = ManagedObjects.OptionBuffers[gameId]; allOptions.Add(new Option(gameId, EndTurn)); #region PlayCardTasks int mana = c.RemainingMana; int zonePosRange = c.BoardZone.Count; bool?spellCostHealth = null; Character[] allTargets = null; Minion[] friendlyMinions = null; Minion[] enemyMinions = null; Minion[] allMinions = null; Character[] allFriendly = null; Character[] allEnemies = null; ReadOnlySpan <IPlayable> handSpan = c.HandZone.GetSpan(); for (int i = 0; i < handSpan.Length; i++) { if (!handSpan[i].ChooseOne || c.ChooseBoth) { GetPlayCardTasks(handSpan[i]); } else { IPlayable[] playables = handSpan[i].ChooseOnePlayables; for (int j = 1; j < 3; j++) { GetPlayCardTasks(handSpan[i], playables[j - 1], j); } } } #endregion #region HeroPowerTask HeroPower power = c.Hero.HeroPower; Card heroPowerCard = power.Card; if (!power.IsExhausted && mana >= power.Cost && !c.HeroPowerDisabled && !heroPowerCard.HideStat) { if (heroPowerCard.ChooseOne) { if (c.ChooseBoth) { allOptions.Add(new Option(gameId, API.Option.Types.PlayerTaskType.HeroPower, source: power)); } else { allOptions.Add(new Option(gameId, API.Option.Types.PlayerTaskType.HeroPower, subOption: 1, source: power)); allOptions.Add(new Option(gameId, API.Option.Types.PlayerTaskType.HeroPower, subOption: 2, source: power)); } } else { if (heroPowerCard.IsPlayableByCardReq(c)) { Character[] targets = GetTargets(heroPowerCard); if (targets != null) { for (int i = 0; i < targets.Length; i++) { allOptions.Add(new Option(gameId, API.Option.Types.PlayerTaskType.HeroPower, 0, Option.getPosition(targets[i], controllerId), source: power, target: targets[i])); } } else { allOptions.Add(new Option(gameId, API.Option.Types.PlayerTaskType.HeroPower, source: power)); } } } } #endregion #region MinionAttackTasks Minion[] attackTargets = null; bool isOpHeroValidAttackTarget = false; var boardSpan = c.BoardZone.GetSpan(); for (int j = 0; j < boardSpan.Length; j++) { Minion minion = boardSpan[j]; if (minion.IsExhausted && (!minion.HasCharge || minion.NumAttacksThisTurn != 0)) { continue; } if (minion.IsFrozen || minion.AttackDamage == 0 || minion.CantAttack || minion.Untouchable) { continue; } GenerateAttackTargets(); for (int i = 0; i < attackTargets.Length; i++) { allOptions.Add(new Option(gameId, MinionAttack, j + 1, Option.getEnemyPosition(attackTargets[i]), source: minion, target: attackTargets[i])); } if (isOpHeroValidAttackTarget && !(minion.CantAttackHeroes || minion.AttackableByRush)) { allOptions.Add(new Option(gameId, MinionAttack, j + 1, Option.OP_HERO_POSITION, source: minion, target: c.Opponent.Hero)); } } #endregion #region HeroAttackTaskts Hero hero = c.Hero; if ((!hero.IsExhausted || (hero.ExtraAttacksThisTurn > 0 && hero.ExtraAttacksThisTurn >= hero.NumAttacksThisTurn)) && hero.AttackDamage > 0 && !hero.IsFrozen) { GenerateAttackTargets(); for (int i = 0; i < attackTargets.Length; i++) { allOptions.Add(new Option(gameId, HeroAttack, 0, Option.getEnemyPosition(attackTargets[i]), source: hero, target: attackTargets[i])); } if (isOpHeroValidAttackTarget && !hero.CantAttackHeroes) { allOptions.Add(new Option(gameId, HeroAttack, 0, Option.OP_HERO_POSITION, source: hero, target: c.Opponent.Hero)); } } #endregion return(allOptions); #region local functions void GetPlayCardTasks(in IPlayable playable, in IPlayable chooseOnePlayable = null, int subOption = -1) { Card card = chooseOnePlayable?.Card ?? playable.Card; if (!spellCostHealth.HasValue) { spellCostHealth = c.ControllerAuraEffects[GameTag.SPELLS_COST_HEALTH] == 1; } bool healthCost = (playable.AuraEffects?.CardCostHealth ?? false) || (spellCostHealth.Value && playable.Card.Type == CardType.SPELL); if (!healthCost && (playable.Cost > mana || playable.Card.HideStat)) { return; } // check PlayableByPlayer switch (playable.Card.Type) { // REQ_MINION_CAP case CardType.MINION when c.BoardZone.IsFull: return; case CardType.SPELL: { if (card.IsSecret) { if (c.SecretZone.IsFull) // REQ_SECRET_CAP { return; } if (c.SecretZone.Any(p => p.Card.AssetId == card.AssetId)) // REQ_UNIQUE_SECRET { return; } } if (card.IsQuest && c.SecretZone.Quest != null) { return; } break; } } { if (!card.IsPlayableByCardReq(c)) { return; } Character[] targets = GetTargets(card); int sourcePosition = playable.ZonePosition; // Card doesn't require any targets if (targets == null) { if (playable is Minion) { for (int i = 0; i <= zonePosRange; i++) { allOptions.Add(new Option(gameId, PlayCard, sourcePosition, i + 1, subOption, source: playable)); } } else { allOptions.Add(new Option(gameId, PlayCard, sourcePosition, -1, subOption, source: playable)); } } else { if (targets.Length == 0) { if (card.MustHaveTargetToPlay) { return; } if (playable is Minion) { for (int i = 0; i <= zonePosRange; i++) { allOptions.Add(new Option(gameId, PlayCard, sourcePosition, i + 1, subOption, source: playable)); } } else { allOptions.Add(new Option(gameId, PlayCard, sourcePosition, -1, subOption, source: playable)); } } else { for (int j = 0; j < targets.Length; j++) { ICharacter target = targets[j]; if (playable is Minion) { //for (int i = 0; i <= zonePosRange; i++) // allOptions.Add(PlayCardTask.Any(c, playable, target, i, subOption, // true)); continue; } else { allOptions.Add(new Option(gameId, PlayCard, sourcePosition, Option.getPosition(target, controllerId), subOption, source: playable, target: target)); } } } } } } // Returns null if targeting is not required // Returns 0 Array if there is no available target Character[] GetTargets(Card card) { // Check it needs additional validation if (!card.TargetingAvailabilityPredicate?.Invoke(c, card) ?? false) { return(null); } Character[] targets; switch (card.TargetingType) { case TargetingType.None: return(null); case TargetingType.All: if (allTargets == null) { if (c.Opponent.Hero.HasStealth) { allTargets = new Character[GetFriendlyMinions().Length + GetEnemyMinions().Length + 1]; allTargets[0] = c.Hero; Array.Copy(GetAllMinions(), 0, allTargets, 1, allMinions.Length); } else { allTargets = new Character[GetFriendlyMinions().Length + GetEnemyMinions().Length + 2]; allTargets[0] = c.Hero; allTargets[1] = c.Opponent.Hero; Array.Copy(GetAllMinions(), 0, allTargets, 2, allMinions.Length); } } targets = allTargets; break; case TargetingType.FriendlyCharacters: if (allFriendly == null) { allFriendly = new Character[GetFriendlyMinions().Length + 1]; allFriendly[0] = c.Hero; Array.Copy(friendlyMinions, 0, allFriendly, 1, friendlyMinions.Length); } targets = allFriendly; break; case TargetingType.EnemyCharacters: if (allEnemies == null) { if (!c.Opponent.Hero.HasStealth) { allEnemies = new Character[GetEnemyMinions().Length + 1]; allEnemies[0] = c.Opponent.Hero; Array.Copy(enemyMinions, 0, allEnemies, 1, enemyMinions.Length); } else { allEnemies = GetEnemyMinions(); } } targets = allEnemies; break; case TargetingType.AllMinions: targets = GetAllMinions(); break; case TargetingType.FriendlyMinions: targets = GetFriendlyMinions(); break; case TargetingType.EnemyMinions: targets = GetEnemyMinions(); break; case TargetingType.Heroes: targets = !c.Opponent.Hero.HasStealth ? new[] { c.Hero, c.Opponent.Hero } : new[] { c.Hero }; break; default: throw new ArgumentOutOfRangeException(); } // Filtering for target_if_available TargetingPredicate p = card.TargetingPredicate; if (p != null) { if (card.Type == CardType.SPELL || card.Type == CardType.HERO_POWER) { Character[] buffer = new Character[targets.Length]; int i = 0; for (int j = 0; j < targets.Length; ++j) { if (!p(targets[j]) || targets[j].CantBeTargetedBySpells) { continue; } buffer[i] = targets[j]; i++; } if (i != targets.Length) { Character[] result = new Character[i]; Array.Copy(buffer, result, i); return(result); } return(buffer); } else { if (!card.TargetingAvailabilityPredicate?.Invoke(c, card) ?? false) { return(null); } Character[] buffer = new Character[targets.Length]; int i = 0; for (int j = 0; j < targets.Length; ++j) { if (!p(targets[j])) { continue; } buffer[i] = targets[j]; i++; } if (i != targets.Length) { Character[] result = new Character[i]; Array.Copy(buffer, result, i); return(result); } return(buffer); } } else if (card.Type == CardType.SPELL || card.Type == CardType.HERO_POWER) { Character[] buffer = new Character[targets.Length]; int i = 0; for (int j = 0; j < targets.Length; ++j) { if (targets[j].CantBeTargetedBySpells) { continue; } buffer[i] = targets[j]; i++; } if (i != targets.Length) { Character[] result = new Character[i]; Array.Copy(buffer, result, i); return(result); } return(buffer); } return(targets); Minion[] GetFriendlyMinions() { return(friendlyMinions ?? (friendlyMinions = c.BoardZone.GetAll())); } Minion[] GetAllMinions() { if (allMinions != null) { return(allMinions); } allMinions = new Minion[GetEnemyMinions().Length + GetFriendlyMinions().Length]; Array.Copy(enemyMinions, allMinions, enemyMinions.Length); Array.Copy(friendlyMinions, 0, allMinions, enemyMinions.Length, friendlyMinions.Length); return(allMinions); } } void GenerateAttackTargets() { if (attackTargets != null) { return; } Minion[] eMinions = GetEnemyMinions(); //var taunts = new Minion[eMinions.Length]; Minion[] taunts = null; int tCount = 0; for (int i = 0; i < eMinions.Length; i++) { if (eMinions[i].HasTaunt) { if (taunts == null) { taunts = new Minion[eMinions.Length]; } taunts[tCount] = eMinions[i]; tCount++; } } if (tCount > 0) { var targets = new Minion[tCount]; Array.Copy(taunts, targets, tCount); attackTargets = targets; isOpHeroValidAttackTarget = false; // some brawls allow taunt heros and c should be fixed return; } attackTargets = eMinions; isOpHeroValidAttackTarget = !c.Opponent.Hero.IsImmune && !c.Opponent.Hero.HasStealth; } Minion[] GetEnemyMinions() { return(enemyMinions ?? (enemyMinions = c.Opponent.BoardZone.GetAll(p => !p.HasStealth && !p.IsImmune))); } #endregion }
internal Card(string id, int assetId, Tag[] tags, Dictionary <PlayReq, int> playRequirements, string[] entourage, Tag[] refTags) { Id = id; AssetId = assetId; Entourage = entourage; PlayRequirements = playRequirements; var tagDict = new Dictionary <GameTag, int>(); var refTagDict = new Dictionary <GameTag, int>(); #region Preprocessing tags. foreach (Tag tag in tags) { if (tag.TagValue.HasIntValue) { tagDict.Add(tag.GameTag, tag.TagValue); switch (tag.GameTag) { case GameTag.COST: Cost = tag.TagValue; break; case GameTag.ATK: ATK = tag.TagValue; break; case GameTag.HEALTH: Health = tag.TagValue; break; case GameTag.OVERLOAD: HasOverload = true; Overload = tag.TagValue; break; case GameTag.SPELLPOWER: SpellPower = tag.TagValue; break; case GameTag.CHOOSE_ONE: ChooseOne = true; break; case GameTag.COMBO: Combo = true; break; case GameTag.TAUNT: Taunt = true; break; case GameTag.CHARGE: Charge = true; break; case GameTag.STEALTH: Stealth = true; break; case GameTag.POISONOUS: Poisonous = true; break; case GameTag.DIVINE_SHIELD: DivineShield = true; break; case GameTag.WINDFURY: Windfury = true; break; case GameTag.LIFESTEAL: LifeSteal = true; break; case GameTag.ECHO: Echo = true; break; case GameTag.RUSH: Rush = true; break; case GameTag.CANT_BE_TARGETED_BY_SPELLS: CantBeTargetedBySpells = true; break; case GameTag.CANT_ATTACK: CantAttack = true; break; case GameTag.MODULAR: Modular = true; break; case GameTag.SECRET: IsSecret = true; break; case GameTag.QUEST: IsQuest = true; break; case GameTag.DEATHRATTLE: Deathrattle = true; break; case GameTag.UNTOUCHABLE: Untouchable = true; break; case GameTag.HIDE_STATS: HideStat = true; break; case GameTag.RECEIVES_DOUBLE_SPELLDAMAGE_BONUS: ReceivesDoubleSpelldamageBonus = true; break; case GameTag.FREEZE: Freeze = true; break; case GameTag.CARDRACE: Race = (Race)(int)tag.TagValue; break; case GameTag.CLASS: Class = (CardClass)(int)tag.TagValue; break; case GameTag.CARDTYPE: Type = (CardType)(int)tag.TagValue; break; } } else if (tag.TagValue.HasBoolValue) { tagDict.Add(tag.GameTag, tag.TagValue ? 1 : 0); } else if (tag.TagValue.HasStringValue) { switch (tag.GameTag) { case GameTag.CARDNAME: Name = tag.TagValue; break; case GameTag.CARDTEXT: Text = tag.TagValue; break; } } } foreach (Tag tag in refTags) { if (refTagDict.ContainsKey(tag.GameTag)) { continue; } if (tag.TagValue.HasIntValue) { refTagDict.Add(tag.GameTag, tag.TagValue); } else if (tag.TagValue.HasBoolValue) { refTagDict.Add(tag.GameTag, tag.TagValue ? 1 : 0); } } #endregion #region Preprocessing requirements int characterType = 0; int friendlyCheck = 0; bool needsTarget = false; foreach (KeyValuePair <PlayReq, int> requirement in playRequirements) { switch (requirement.Key) { case PlayReq.REQ_TARGET_TO_PLAY: MustHaveTargetToPlay = true; needsTarget = true; break; case PlayReq.REQ_DRAG_TO_PLAY: // TODO case PlayReq.REQ_NONSELF_TARGET: case PlayReq.REQ_TARGET_IF_AVAILABLE: needsTarget = true; break; case PlayReq.REQ_MINION_TARGET: characterType = 1; break; case PlayReq.REQ_FRIENDLY_TARGET: friendlyCheck = 1; break; case PlayReq.REQ_ENEMY_TARGET: friendlyCheck = -1; break; case PlayReq.REQ_HERO_TARGET: characterType = -1; break; case PlayReq.REQ_TARGET_WITH_RACE: TargetingPredicate += TargetingPredicates.ReqTargetWithRace(requirement.Value); break; case PlayReq.REQ_FROZEN_TARGET: TargetingPredicate += TargetingPredicates.ReqFrozenTarget; break; case PlayReq.REQ_DAMAGED_TARGET: TargetingPredicate += TargetingPredicates.ReqDamagedTarget; break; case PlayReq.REQ_UNDAMAGED_TARGET: TargetingPredicate += TargetingPredicates.ReqUndamagedTarget; break; case PlayReq.REQ_TARGET_MAX_ATTACK: TargetingPredicate += TargetingPredicates.ReqTargetMaxAttack(requirement.Value); break; case PlayReq.REQ_TARGET_MIN_ATTACK: TargetingPredicate += TargetingPredicates.ReqTargetMinAttack(requirement.Value); break; case PlayReq.REQ_MUST_TARGET_TAUNTER: TargetingPredicate += TargetingPredicates.ReqMustTargetTaunter; break; case PlayReq.REQ_STEALTHED_TARGET: TargetingPredicate += TargetingPredicates.ReqStealthedTarget; break; case PlayReq.REQ_TARGET_WITH_DEATHRATTLE: TargetingPredicate += TargetingPredicates.ReqTargetWithDeathrattle; break; case PlayReq.REQ_LEGENDARY_TARGET: TargetingPredicate += TargetingPredicates.ReqLegendaryTarget; break; case PlayReq.REQ_TARGET_FOR_COMBO: needsTarget = true; TargetingAvailabilityPredicate += TargetingPredicates.ReqTargetForCombo; break; case PlayReq.REQ_TARGET_IF_AVAILABE_AND_ELEMENTAL_PLAYED_LAST_TURN: needsTarget = true; TargetingAvailabilityPredicate += TargetingPredicates.ElementalPlayedLastTurn; break; case PlayReq.REQ_TARGET_IF_AVAILABLE_AND_DRAGON_IN_HAND: needsTarget = true; TargetingAvailabilityPredicate += TargetingPredicates.DragonInHand; break; case PlayReq.REQ_TARGET_IF_AVAILABLE_AND_MINIMUM_FRIENDLY_MINIONS: needsTarget = true; TargetingAvailabilityPredicate += TargetingPredicates.MinimumFriendlyMinions(requirement.Value); break; case PlayReq.REQ_TARGET_IF_AVAILABLE_AND_MINIMUM_FRIENDLY_SECRETS: needsTarget = true; TargetingAvailabilityPredicate += TargetingPredicates.MinimumFriendlySecrets(requirement.Value); break; case PlayReq.REQ_TARGET_IF_AVAILABLE_AND_NO_3_COST_CARD_IN_DECK: // TODO TargetingType = TargetingType.AllMinions; break; case PlayReq.REQ_NUM_MINION_SLOTS: PlayAvailabilityPredicate += TargetingPredicates.ReqNumMinionSlots; break; case PlayReq.REQ_MINIMUM_ENEMY_MINIONS: PlayAvailabilityPredicate += TargetingPredicates.ReqMinimumEnemyMinions(requirement.Value); break; case PlayReq.REQ_MINIMUM_TOTAL_MINIONS: PlayAvailabilityPredicate += TargetingPredicates.ReqMinimumTotalMinions(requirement.Value); break; case PlayReq.REQ_HAND_NOT_FULL: PlayAvailabilityPredicate += TargetingPredicates.ReqHandNotFull; break; case PlayReq.REQ_WEAPON_EQUIPPED: PlayAvailabilityPredicate += TargetingPredicates.ReqWeaponEquipped; break; case PlayReq.REQ_ENTIRE_ENTOURAGE_NOT_IN_PLAY: PlayAvailabilityPredicate += TargetingPredicates.ReqEntireEntourageNotInPlay(assetId); break; case PlayReq.REQ_FRIENDLY_MINION_DIED_THIS_GAME: PlayAvailabilityPredicate += TargetingPredicates.ReqFriendlyMinionDiedThisGame; break; case PlayReq.REQ_MUST_PLAY_OTHER_CARD_FIRST: PlayAvailabilityPredicate += c => false; break; // REQ_STEADY_SHOT // REQ_MINION_OR_ENEMY_HERO // Steady Shot // REQ_MINION_SLOT_OR_MANA_CRYSTAL_SLOT // Jade Blossom case PlayReq.REQ_SECRET_ZONE_CAP_FOR_NON_SECRET: PlayAvailabilityPredicate += TargetingPredicates.ReqSecretZoneCapForNonSecret; break; } } if (needsTarget) { if (characterType > 0) { if (friendlyCheck == 0) { TargetingType = TargetingType.AllMinions; } else if (friendlyCheck == 1) { TargetingType = TargetingType.FriendlyMinions; } else { TargetingType = TargetingType.EnemyMinions; } } else if (characterType == 0) { if (friendlyCheck == 0) { TargetingType = TargetingType.All; } else if (friendlyCheck == 1) { TargetingType = TargetingType.FriendlyCharacters; } else { TargetingType = TargetingType.EnemyCharacters; } } else { TargetingType = TargetingType.Heroes; } } #endregion Tags = tagDict; RefTags = refTagDict; // spell damage information add ... if (Text != null && (Text.Contains("$") || tagDict.ContainsKey(GameTag.AFFECTED_BY_SPELL_POWER))) { Text += " @spelldmg"; IsAffectedBySpellDamage = true; } }