Esempio n. 1
0
        static List <CardAIPlanData> RefineMultiCastPlan(CardAI ai, CardAIPlanData entryState, List <CardAIPlanData> plans)
        {
            NetBattlefield bf = entryState.bf;

            //get top plans and try to refine them
            int min = Mathf.Min(plans.Count, ai.refiningSize);

            for (int i = 0; i < min; i++)
            {
                for (int k = 0; k < ai.intensity; k++)
                {
                    //prepare source for cast copying target enumerator so that it can be reused by the further casts
                    CardAIPlanData source = entryState;
                    NetSkill       ns     = bf.GetSkillByID(plans[i].GetTopCardPlay().skill);

                    //cast form the source plan data so that it is not casted "after" action we want to reiterate to different targets
                    CardAIPlanData d = CastSkillOnece(ai, ns, ns.GetOwner(bf), source, bf.CloneAndRemember(), plans[i].GetTopCardPlay().targetEnumerator);
                    if (d.valid && plans[i].value < d.value)
                    {
                        d.SetEnumerator(plans[i].GetTopCardPlay().targetEnumerator);
                        plans[i] = d;
                    }
                }
            }

            //select best plan per skill
            return(plans.GetRange(0, min));
        }
Esempio n. 2
0
        static public List <CardAIPlanData> ProduceCastPlans(CardAI ai, CardAIPlanData data, List <NetSkill> skills, int count)
        {
            List <CardAIPlanData> plans = new List <CardAIPlanData>(skills.Count);
            NetBattlefield        bf    = data.bf;

            for (int i = 0; i < skills.Count; i++)
            {
                NetSkill ns = skills[i];
                IEnumerator <List <int> > targetEnumerator = null;

                for (int k = 0; k < count; k++)
                {
                    CardAIPlanData d = CastSkillOnece(ai, ns, ns.GetOwner(bf), data, bf.CloneAndRemember(), targetEnumerator);
                    if (d.valid)
                    {
                        if (targetEnumerator == null)
                        {
                            targetEnumerator = d.GetTopCardPlay().targetEnumerator;
                        }
                        plans.Add(d);
                    }
                }
            }

            return(plans);
        }
Esempio n. 3
0
        static List <int> GetSecondaryPlayTargets(NetSkill ns, NetCard nc, NetBattlefield bf, List <int> primaryTargets, MHRandom r)
        {
            if (primaryTargets == null ||
                primaryTargets.Count < 1)
            {
                return(null);
            }

            Subskill ss = ns.GetSubSkill();

            if (ss.trigger.triggerGroup == ETriggerGroupType.DoAttack ||
                ss.trigger.triggerGroup == ETriggerGroupType.DoAlternateAttack)
            {
                //there is no secondary targets of playing card to the battelfield unless its a spell
                return(null);
            }

            if (ss.targets.script2 == null ||
                string.IsNullOrEmpty(ss.targets.script2.scriptName))
            {
                return(null);
            }

            return(ns.FindSecondaryTargets(bf, -1, primaryTargets));
        }
Esempio n. 4
0
        static NetBattlefield ProduceFakeBFIfNeeded(int playerID, NetBattlefield bf)
        {
            //fake battlefield will contain all friendly cards currently not on battlefield
            //positioned on free positions so that AI can see if it can produce
            //any targets. there is no simulation involved so fake state should not harm "thinking"

            //we will not need fake BF IF all owned cards are already on BF.
            HashSet <NetCard> ownCastedCards = bf.GetCardsOnBattelfield(playerID);
            NetCardPlayerData ncd            = bf.GetPlayerByPlayerID(playerID);

            if (ncd == null || NetType.IsNullOrEmpty(ncd.HandCards))
            {
                return(bf);
            }

            List <int>     ownCardsAtHand = ncd.HandCards.value;
            List <NetCard> cardsToCast    = new List <NetCard>();

            //this is different set than just cards which were used, because card which casts summons or
            //does support will not be present on battlefield and still cost more than base AP
            foreach (var v in ownCardsAtHand)
            {
                NetCard nc = bf.GetCardByID(v);
                if (!ownCastedCards.Contains(nc))
                {
                    cardsToCast.Add(nc);
                }
            }

            if (cardsToCast.Count == 0)
            {
                return(bf);
            }

            //there are cards which should be represented on battle positions
            //which requires new BF
            //we would do simple but fake casts
            NetBattlefield bf2      = bf.CloneAndRemember();
            int            halfSize = bf2.BattlefieldSize / 2;
            int            start    = playerID == 0 ? 0 : halfSize;

            int cardListIndex = 0;

            for (int i = 0; i < halfSize; i++)
            {
                if (cardListIndex == cardsToCast.Count)
                {
                    break;
                }
                int index = i + start;
                if (bf2.BattlefieldPositions.value[index] == 0)
                {
                    bf2.BattlefieldPositions.value[index] = cardsToCast[cardListIndex].CardID;
                }
            }

            return(bf2);
        }
Esempio n. 5
0
        static CardAIPlanData ProduceSingleRefination(CardAI ai, CardAIPlanData entryState, CardAIPlanData plan)
        {
            NetBattlefield bf = entryState.bf;
            NetSkill       ns = bf.GetSkillByID(plan.GetTopCardPlay().skill);

            //cast form the source plan data so that it is not casted "after" action we want to reiterate to different targets
            CardAIPlanData d = CastSkillOnece(ai, ns, ns.GetOwner(bf), entryState, bf.CloneAndRemember(), plan.GetTopCardPlay().targetEnumerator);

            if (d.valid && plan.value < d.value)
            {
                d.SetEnumerator(plan.GetTopCardPlay().targetEnumerator);
            }
            return(d);
        }
Esempio n. 6
0
        // Helper that applies normal damage and shield leech (affected by leechFactor).
        static public object Act_ShieldLeech_Helper(NetBattlefield bf, NetQueueItem q, FInt leechFactor)
        {
            NetSkill ns    = q.GetNetSkill(bf);
            NetCard  owner = bf.GetCardByID(ns.OwnerCardID);

            NetCard target = null;

            if (NetType.IsNullOrEmpty(q.Targets))
            {
                return(null);
            }

            FInt damage        = owner.GetSkillCastingStrength(ns);
            int  shieldLeeched = 0;

            // Primary targets.
            foreach (var v in q.Targets.value)
            {
                target = bf.ConvertPositionIDToCard(v);
                if (target != null)
                {
                    target.ReciveNormalDamage(damage, bf, q, v);
                    shieldLeeched += (damage * leechFactor).ToInt();
                }
            }

            // Secondary targets.
            if (!NetType.IsNullOrEmpty(q.SecondaryTargets))
            {
                damage *= 0.5f;

                foreach (var v in q.SecondaryTargets.value)
                {
                    target = bf.ConvertPositionIDToCard(v);
                    if (target != null)
                    {
                        target.ReciveNormalDamage(damage, bf, q, v);
                        shieldLeeched += (damage * leechFactor).ToInt();
                    }
                }
            }

            if (shieldLeeched > 0)
            {
                FInt currentShields = owner.GetCA_SHIELD();
                owner.SetCA_SHIELD(currentShields + shieldLeeched);
            }

            return(null);
        }
Esempio n. 7
0
        // Ancient version of the built-in script Act_DrainHealthEssence, which leeches 80% instead.
        // Note: Built-in script Act_DrainHealthAncient does not appear to do this.
        static public object Act_LifeLeech_Ancient(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random)
        {
            NetSkill ns    = q.GetNetSkill(bf);
            NetCard  owner = bf.GetCardByID(ns.OwnerCardID);

            NetCard target = null;

            if (NetType.IsNullOrEmpty(q.Targets))
            {
                return(null);
            }

            FInt damage      = owner.GetSkillCastingStrength(ns);
            int  lifeLeeched = 0;

            // Primary targets.
            foreach (var v in q.Targets.value)
            {
                target = bf.ConvertPositionIDToCard(v);
                if (target != null)
                {
                    target.ReciveNormalDamage(damage, bf, q, v);
                    lifeLeeched += (damage * 0.8f).ToInt();
                }
            }

            // Secondary targets.
            if (!NetType.IsNullOrEmpty(q.SecondaryTargets))
            {
                damage *= 0.5f;

                foreach (var v in q.SecondaryTargets.value)
                {
                    target = bf.ConvertPositionIDToCard(v);
                    if (target != null)
                    {
                        target.ReciveNormalDamage(damage, bf, q, v);
                        lifeLeeched += (damage * 0.8f).ToInt();
                    }
                }
            }

            if (lifeLeeched > 0)
            {
                owner.ReciveHealthNormal(lifeLeeched, bf, q, t);
            }

            return(null);
        }
Esempio n. 8
0
        //
        //         static public CardAIPlanData _ProducePlan(int playerID, CardAIPlanData data, int avaliableAP, MHRandom r, int calculationIntensity = 1)
        //         {
        //             if (calculationIntensity < 1 )
        //             {
        //                 Debug.LogError("[ERROR]0 level for AI will nto produce any plans!, Increase calculation intensity to minimum 1!");
        //                 return new CardAIPlanData();
        //             }
        //
        //             NetBattlefield bf = data.bf;
        //
        //             List<NetCard> cards = CardsWithinAPRange(playerID, bf, avaliableAP);
        //             if (cards == null) return new CardAIPlanData();
        //
        //             List<NetSkill> skills = SelectSkillsToCast(cards);
        //             if (skills == null || skills.Count == 0) return new CardAIPlanData();
        //
        //             bool friendlyTurn = playerID > 0;
        //
        //             //Do single cast of all skills to build some expectations
        //             CardAIPlanData[] plans = new CardAIPlanData[skills.Count];
        //
        //             for (int i=0; i<skills.Count; i++)
        //             {
        //                 NetSkill ns = skills[i];
        //                 CardAIPlanData d = CastSkillOnece(friendlyTurn, ns, ns.GetOwner(bf), data, bf.CloneAndRemember(), r);
        //                 if (d.valid)
        //                 {
        //                     plans[i] = d;
        //                 }
        //             }
        //
        //             //sort result based on their value
        //             List<CardAIPlanData> plansL = new List<CardAIPlanData>(plans);
        //             plansL.Sort(delegate (CardAIPlanData a, CardAIPlanData b)
        //             {
        //                 return a.value.CompareTo(b.value);
        //             });
        //
        //             //get top plans and try to refine them before selecting the best
        //             int min = Mathf.Min(plansL.Count, calculationIntensity);
        //             plansL = plansL.GetRange(0, min);
        //
        //             int refiningLevel = 8;
        //             plans = new CardAIPlanData[plansL.Count * refiningLevel];
        //             for(int i=0; i < plansL.Count; i++)
        //             {
        //                 for(int k=0; k< refiningLevel; k++)
        //                 {
        //                     //plansL[i].validTargets
        //                 }
        //             }
        //
        //             // Start secondary plans from the selected plans
        //             return new CardAIPlanData();
        //         }
        static public List <NetCard> CardsWithinAPRange(CardAI ai, NetBattlefield bf, int testedAPRange)
        {
            NetListInt ni = bf.GetPlayerByPlayerID(ai.playerID).HandCards;

            if (NetType.IsNullOrEmpty(ni))
            {
                return(null);
            }
            List <NetCard> ncs = new List <NetCard>();

            foreach (var v in ni.value)
            {
                NetCard nc = bf.GetCardByID(v);
                if (nc.GetCastingCost() <= testedAPRange)
                {
                    ncs.Add(nc);
                }
            }
            return(ncs);
        }
Esempio n. 9
0
        static List <NetSkill> WillDoTwoCastSimulation(CardAI ai,
                                                       CardAIPlanData data,
                                                       List <NetSkill> consideredSkills)
        {
            List <NetSkill> supportSkills = new List <NetSkill>();

            //if there is no skills which can be used there is no activity to consider
            if (consideredSkills == null || consideredSkills.Count < 1)
            {
                return(supportSkills);
            }

            NetBattlefield bf = null;

            foreach (var v in consideredSkills)
            {
                if (v.IsCastingSpell())
                {
                    if (bf == null)
                    {
                        bf = ProduceFakeBFIfNeeded(ai.playerID, data.bf);
                    }

                    List <int> pos = v.FindValidTargets(bf, -1);
                    if (pos != null)
                    {
                        if (pos.FindIndex(o => bf.IsBattleSlot(o) &&
                                          !bf.IsSlotFree(o) &&
                                          bf.IsSameSideSlot(ai.playerID, o)) == -1)
                        {
                            //this skill does not have allied targets
                            continue;
                        }

                        supportSkills.Add(v);
                    }
                }
            }

            return(supportSkills);
        }
        /// <summary>
        /// Target any enemy character
        /// </summary>
        /// <param name="bf"></param>
        /// <param name="ns"></param>
        /// <param name="bfPosition"></param>
        /// <returns></returns>
        static public List <int> Trg_RangeTargeting_Unblocked(NetBattlefield bf, NetSkill ns, int bfPosition)
        {
            if (bf == null || ns == null)
            {
                return(null);
            }

            NetCard nc = bf.GetCardByID(ns.OwnerCardID);

            if (nc == null)
            {
                return(null);
            }

            //this skill should work only when activated from battlefield
            if (!bf.IsBattleSlot(bfPosition))
            {
                return(null);
            }

            return(SubskillScript.TrgU_FallbackTargets(bf, ns, nc));
        }
Esempio n. 11
0
        static List <CardAIPlanData> ProduceRefinePlans(CardAI ai, CardAIPlanData entryState, List <CardAIPlanData> plans)
        {
            NetBattlefield bf = entryState.bf;

            //get top plans and try to refine them
            int min = Mathf.Min(plans.Count, ai.refiningSize);

            for (int i = 0; i < min; i++)
            {
                for (int k = 0; k < ai.intensity; k++)
                {
                    CardAIPlanData d = ProduceSingleRefination(ai, entryState, plans[i]);
                    if (d.valid && plans[i].value < d.value)
                    {
                        d.SetEnumerator(plans[i].GetTopCardPlay().targetEnumerator);
                        plans[i] = d;
                    }
                }
            }

            //select best plan per skill
            return(plans.GetRange(0, min));
        }
Esempio n. 12
0
        static IList <int> GetPlayCardTargets(bool friendlyTurn, NetSkill ns, NetCard nc, NetBattlefield bf)
        {
            Subskill ss = ns.GetSubSkill();

            if (ss.trigger.triggerGroup == ETriggerGroupType.DoAttack ||
                ss.trigger.triggerGroup == ETriggerGroupType.DoAlternateAttack)
            {
                if (ss.trigger.requiredToBeInFrontline)
                {
                    if (friendlyTurn)
                    {
                        return(bf.GetFreeFriendlyMeleeSlots(false));
                    }
                    else
                    {
                        return(bf.GetFreeEnemyMeleeSlots(false));
                    }
                }
                else if (ss.trigger.requiredToBeInBackline)
                {
                    if (friendlyTurn)
                    {
                        return(bf.GetFreeFriendlyRangedSlots(false));
                    }
                    else
                    {
                        return(bf.GetFreeEnemyRangedSlots(false));
                    }
                }
                else
                {
                    List <int> both;
                    if (friendlyTurn)
                    {
                        IList <int> a = bf.GetFreeFriendlyMeleeSlots(false);
                        IList <int> b = bf.GetFreeFriendlyRangedSlots(false);
                        both = new List <int>(a.Count + b.Count);
                        both.AddRange(a);
                        both.AddRange(b);
                        return(both);
                    }
                    else
                    {
                        IList <int> a = bf.GetFreeEnemyMeleeSlots(false);
                        IList <int> b = bf.GetFreeEnemyRangedSlots(false);
                        both = new List <int>(a.Count + b.Count);
                        both.AddRange(a);
                        both.AddRange(b);
                        return(both);
                    }
                }
            }
            else
            {
                return(ns.FindValidTargets(bf, -1));
            }
        }
Esempio n. 13
0
        static CardAIPlanData CastSkillOnece(CardAI ai, NetSkill ns, NetCard nc, CardAIPlanData data, NetBattlefield bf, IEnumerator <List <int> > targetEnumerator)
        {
            //structure makes a new copy
            CardAIPlanData result = data;
            //using previous bf allows to utilize already cached data, as well as share caching with following casts.
            //later we will use new bf to ensure we do not override the same bf.
            NetBattlefield prevBf = data.bf;

            IList <int> targets = GetPlayCardTargets(ai.FriendlyTurn, ns, nc, prevBf);

            if (targets == null || targets.Count < 1)
            {
                return(new CardAIPlanData());
            }

            if (targetEnumerator == null)
            {
                targetEnumerator = GetTargetEnumerator(ns, nc, targets, ai);
                if (targetEnumerator == null)
                {
                    return(new CardAIPlanData());
                }
            }

            targetEnumerator.MoveNext();
            List <int> selectedTargets = targetEnumerator.Current;

            if (selectedTargets == null || selectedTargets.Count == 0)
            {
                return(new CardAIPlanData());
            }

            List <int> secTargets = GetSecondaryPlayTargets(ns, nc, prevBf, selectedTargets, ai.r);
            FInt       skillDelay = ns.GetSkillDelay(prevBf);
            int        cost       = prevBf.GetCardCastingCostFast(ns.GetOwner(prevBf).CardID);
            //add cost if something need to be casted next turn by AI
            var ncp = bf.GetPlayerByPlayerID(ai.playerID);

            if (cost > ncp.ApLeft)
            {
                skillDelay += 2;
            }

            //operate from now on its own bf
            result.bf = bf;
            NetQueueItem q;

            if (ns.IsCastingSpell())
            {
                q = new NetQueueItem(bf, ns, new NetListInt(selectedTargets), new NetListInt(secTargets), skillDelay, -1);
            }
            else
            {
                q = new NetQueueItem(bf, ns, null, null, skillDelay, selectedTargets[0]);
            }



            //if ap cost were larger than current ap then we will result in negative ap.
            //because its just simulation we do not care for that now, as the sum of this and next turn ap
            //for estimating cost would be identical
            ncp.ApLeft -= nc.GetCastingCost();

            bf.PlayCard(q, ai.r);
            float value = bf.GetValueByCloneSimulation(ai.iterations, ai.r);

            result.value = value;
            result.AddCardPlay(nc.CardID, ns.SkillInBattleID, q);
            result.SetEnumerator(targetEnumerator);
            result.valid = true;

            return(result);
        }
        static public object Act_AddShieldingImproved(
            NetBattlefield bf,
            NetQueueItem q,
            List <NetQueueItem> stack,
            List <NetQueueItem> previousItems,
            MHRandom random)
        {
            NetSkill ns    = q.GetNetSkill(bf);
            NetCard  owner = bf.GetCardByID(ns.OwnerCardID);

            //skill needs target(-s)
            if (!NetTypeAtomic.IsValid(q.Targets))
            {
                return(null);
            }
            FInt extra = FInt.ZERO;

            if (ns.MainAtt != null)
            {
                extra = owner.GetSkillCastingStrength(ns);
            }

            FInt   value = ns.GetFloatAttribute("TAG-CA_SHIELD");
            string sign  = ns.GetStringAttribute("TAG-CA_SHIELD");

            if (value == FInt.ZERO)
            {
                if (bf.ChallengeType == EChallengeType.TypePhysical)
                {
                    value = ns.GetFloatAttribute("TAG-SHIELDING_PHYSICAL");
                }
                else if (bf.ChallengeType == EChallengeType.TypeMental)
                {
                    value = ns.GetFloatAttribute("TAG-SHIELDING_MENTAL");
                }
                else if (bf.ChallengeType == EChallengeType.TypeSpirit)
                {
                    value = ns.GetFloatAttribute("TAG-SHIELDING_SPIRIT");
                }
            }
            if (string.IsNullOrEmpty(sign))
            {
                if (bf.ChallengeType == EChallengeType.TypePhysical)
                {
                    sign = ns.GetStringAttribute("TAG-SHIELDING_PHYSICAL");
                }
                else if (bf.ChallengeType == EChallengeType.TypeMental)
                {
                    sign = ns.GetStringAttribute("TAG-SHIELDING_MENTAL");
                }
                else if (bf.ChallengeType == EChallengeType.TypeSpirit)
                {
                    sign = ns.GetStringAttribute("TAG-SHIELDING_SPIRIT");
                }
            }
            value += extra;

            value.CutToInt();
            if (sign == "*")
            {
                value = value * 0.01f;
            }

            foreach (var v in q.Targets.value)
            {
                NetCard target = bf.ConvertPositionIDToCard(v);
                if (target == null)
                {
                    continue;
                }
                FInt prev = target.GetCA_SHIELD();

                if (sign == "+")
                {
                    target.SetCA_SHIELD(value + prev);
                }
                else if (sign == "*")
                {
                    target.SetCA_SHIELD(value * prev);
                }
            }

            if (NetType.IsNullOrEmpty(q.SecondaryTargets))
            {
                return(null);
            }

            foreach (var v in q.SecondaryTargets.value)
            {
                NetCard target = bf.ConvertPositionIDToCard(v);
                if (target == null)
                {
                    continue;
                }
                FInt prev = target.GetCA_SHIELD();

                if (sign == "+")
                {
                    target.SetCA_SHIELD(value + prev);
                }
                else if (sign == "*")
                {
                    target.SetCA_SHIELD(value * prev);
                }
            }

            return(null);
        }
Esempio n. 15
0
 // TODO: I'm not sure if this needs a special import to reference the built-in script.
 static public object Act_LifeAndShieldLeech_Essence(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random)
 {
     // Act_DrainHealthEssence(bf, q, stack, previousItems, random);
     return(Act_ShieldLeech_Essence(bf, q, stack, previousItems, random));
 }
Esempio n. 16
0
 static public object Act_LifeAndShieldLeech_Ancient(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random)
 {
     Act_LifeLeech_Ancient(bf, q, stack, previousItems, random);
     return(Act_ShieldLeech_Ancient(bf, q, stack, previousItems, random));
 }
Esempio n. 17
0
 static public object Act_ShieldLeech_Ancient(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random)
 {
     return(Act_ShieldLeech_Helper(bf, q, 1.0f));
 }
Esempio n. 18
0
        // AI 2.0

        #region Core planning
        static public CardAIPlanData ProducePlan(CardAI ai, CardAIPlanData data, bool allowTwoStages)
        {
            if (ai.intensity < 1)
            {
                Debug.LogError("[ERROR]0 level for AI will not produce any plans!, Increase calculation intensity to minimum 1!");
                return(new CardAIPlanData());
            }

            NetBattlefield bf         = data.bf;
            int            apNextTurn = bf.APNextTurn(ai.playerID);
            int            leftAP     = bf.LeftAPThisTurn(ai.playerID) + apNextTurn;
            float          value      = bf.GetValueByCloneSimulation(ai.iterations, ai.r);

            List <NetCard> cards = CardsWithinAPRange(ai, bf, ai.totalAP);

            if (cards == null)
            {
                return(new CardAIPlanData());
            }

            List <NetSkill> supportSkills2 = new List <NetSkill>();
            List <NetSkill> skills         = SelectSkillsToCast(cards);

            if (skills == null || skills.Count == 0)
            {
                Debug.LogWarning("[!! AI] Avaliable skills to cast = 0");
                return(new CardAIPlanData());
            }
            List <NetSkill> skills2      = null;
            List <int>      importantPos = null;

            #region detect potential two-cast suggestions
            if (allowTwoStages)
            {
                List <NetCard> cards2 = CardsWithinAPRange(ai, bf, ai.totalAP - 1);
                if (cards2 != null)
                {
                    skills2 = SelectSkillsToCast(cards2);
                    //two cast simulation would be considered only in case of at least two skills
                    //being of value for that activity
                    if (skills2 != null && skills2.Count > 1)
                    {
                        supportSkills2 = WillDoTwoCastSimulation(ai, data, skills2);
                        if (supportSkills2.Count > 0)
                        {
                            importantPos = FindImportantPositions(ai, data, supportSkills2);
                        }

                        //expensive casts which exhausts available AP will be casted as single-casts
                        //support skills will be casted as single-casts
                        //cheap enough non-supports will be BASE for multi-casts
                        //then supports skills will be casted on top
                        skills2 = skills2.Except(supportSkills2).ToList();
                        skills  = skills.Except(skills2).ToList();
                    }
                }
            }
            #endregion

            #region produce single-cast results

            List <CardAIPlanData> plans = ProduceCastPlans(ai, data, skills, 1);
            if (plans != null && plans.Count > 0)
            {
                plans.Sort(PlanSorter);
                plans = ProduceRefinePlans(ai, data, plans);
            }

            #endregion
            #region produce two-cast results.
            //TODO start by doing first layer of casts, with preferences if possible

            if (skills2 != null && skills2.Count > 0)
            {
                List <CardAIPlanData> plans2 = ProduceCastPlans(ai, data, skills2, 1);
                if (plans2 != null && plans2.Count > 0)
                {
                    plans2.Sort(PlanSorter);
                    plans2 = ProduceRefinePlans(ai, data, plans2);
                    plans.AddRange(plans2);
                }

                //if supports are possible validate do another single turn pass for remaining skills.
                if (supportSkills2 != null && supportSkills2.Count > 0)
                {
                    //register important positions if any to ensure they are preferred during position planning.
                    ai.preferredStrategicPlaces = importantPos;

                    //First Cast
                    plans2 = ProduceCastPlans(ai, data, skills2, 1);
                    if (plans2 != null && plans2.Count > 0)
                    {
                        plans2.Sort(PlanSorter);
                        plans2 = ProduceRefinePlans(ai, data, plans2);

                        //Second Cast (aka support cast)
                        //Use first plan and select boosts which seems to be most efficient among available options.
                        //this does one base cast and then one boost for each which account for: AxB
                        List <CardAIPlanData> supportPlans = new List <CardAIPlanData>();
                        if (plans2 != null && plans2.Count > 0)
                        {
                            for (int i = 0; i < ai.supportCasts && i < plans2.Count; i++)
                            {
                                CardAIPlanData plan = plans2[i];
                                supportPlans.AddRange(ProduceCastPlans(ai, plan, supportSkills2, 1));
                            }
                        }

                        // select the best result of AxB to find the best theoretical result after two casts
                        //do deeper planning for the base positioning and then cast support skills on the chosen plan to
                        //acquire actual final value
                        supportPlans.Sort(PlanSorter);

                        if (supportPlans.Count > 0)
                        {
                            CardAIPlanData cpd      = supportPlans[0];
                            CardAIPlanData planBase = plans2.Find(o => o.firstPlay.skill == cpd.firstPlay.skill);

                            CardAIPlanData result = ProduceSingleRefination(ai, data, planBase);
                            if (result.valid)
                            {
                                List <CardAIPlanData> results = ProduceCastPlans(ai, result, supportSkills2, 1);
                                if (results != null && results.Count > 0)
                                {
                                    results.Sort(PlanSorter);
                                    results = ProduceRefinePlans(ai, data, results);

                                    plans.AddRange(results);
                                }
                            }
                        }
                    }
                }
            }
            #endregion

            #region compare all results and select the best
            // all results are compared based on how much AP they use.
            // positive gain produced by all results are based on final result and final AP cost
            // GainedAdvantage / (2 + AP used) which results in:
            // 1/2 multiplier for 1 AP actions
            // 1/3 multiplier for 2 AP actions
            // 1/4 multiplier for 3+ AP actions
            // Single-casts would be considered as if they would use-up AvaliableAP amount
            // to ensure we do not consider them better even though they cannot provide any better results
            //if two-cast actions are available to some actions
            // aka: there is no point in having cheaper cost if AP cannot be later used.

            if (plans.Count == 0)
            {
                Debug.LogWarning("[!! AI] Available plans = 0");
                return(new CardAIPlanData());
            }


            CardAIPlanData selected  = plans[0];
            float          selectedV = float.MinValue;

            for (int i = 0; i < plans.Count; i++)
            {
                CardAIPlanData cpd       = plans[i];
                int            newLeftAP = cpd.bf.LeftAPThisTurn(ai.playerID) + apNextTurn;
                int            apUsed    = leftAP - newLeftAP;
                float          v         = cpd.value - value;
                v = v / (1f + apUsed);
                if (selectedV < v)
                {
                    selected  = plans[i];
                    selectedV = v;
                }
            }
            #endregion

            return(selected);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="bf">general battlefield information</param>
        /// <param name="q">queue element which is already executed which trigers this skill</param>
        /// <param name="stack">Items which are still in stack including "q", which most likely is first element</param>
        /// <param name="previousItems">Queue items which were already executed in order they were executed</param>
        /// <param name="random">random generator specific to this thread</param>
        /// <returns></returns>
        /// <summary>
        /// Basic damage
        /// </summary>
        static public object Act_Damage_Procedural(
            NetBattlefield bf,
            NetQueueItem q,
            List <NetQueueItem> stack,
            List <NetQueueItem> previousItems,
            MHRandom random)
        {
            NetSkill ns    = q.GetNetSkill(bf);
            NetCard  owner = bf.GetCardByID(ns.OwnerCardID);

            NetCard target = null;

            if (NetType.IsNullOrEmpty(q.Targets))
            {
                return(null);
            }

            int  flags   = ns.Flags;
            bool essence = (flags & (int)EActivatorBlocks.Essence) > 0;
            bool ancient = (flags & (int)EActivatorBlocks.Ancient) > 0;
            bool gray    = !ancient && !essence;

            FInt damage = GameplayUtils.GetDamageFor(ns, owner);
//             if ((flags & (int)SkillGenerator.EActivatorBlocks.Additive) > 0)
//             {
//
//                 if (essence)
//                 {
//                     //essence is estimated to be equal between additive and multiplicative at
//                     // value of 10  (A x 0.2 + 8) ~ (A = 10)
//                     damage = owner.GetSkillCastingAdditiveStrength(ns, SkillGenerator.ESSENCE_ADDITIVE_BASE);
//                 }
//                 else if(ancient)
//                 {
//                     //essence is estimated to be equal between additive and multiplicative at
//                     // value of 15  (A x 0.2 + 12) ~ (A = 15)
//                     damage = owner.GetSkillCastingAdditiveStrength(ns, SkillGenerator.ANCIENT_ADDITIVE_BASE);
//                 }
//                 else
//                 {
//                     //essence is estimated to be equal between additive and multiplicative at
//                     // value of 6  (A x 0.2 + 5) ~ (A = 6)
//                     damage = owner.GetSkillCastingAdditiveStrength(ns, SkillGenerator.GRAY_ADDITIVE_BASE);
//                 }
//             }
//             else
//             {
//                 damage = owner.GetSkillCastingStrength(ns);
//             }

            bool trueDamage   = (flags & (int)EActivatorBlocks.TrueDamage) > 0;
            bool poisonDamage = (flags & (int)EActivatorBlocks.PoisonDamage) > 0;
            bool lifeLeech    = (flags & (int)EActivatorBlocks.LifeLeech) > 0;
            bool shieldLeech  = (flags & (int)EActivatorBlocks.ShieldLeech) > 0;
            bool additive     = (flags & (int)EActivatorBlocks.Additive) > 0;

            int lifeLeeched   = 0;
            int shieldLeeched = 0;

            #region Primary targets
            foreach (var v in q.Targets.value)
            {
                target = bf.ConvertPositionIDToCard(v);
                if (target != null)
                {
                    FInt dmg = damage;
                    if (poisonDamage && target.IsWounded())
                    {
                        if (essence)
                        {
                            dmg *= 1.35f;
                        }
                        else
                        {
                            dmg *= 1.6f;
                        }
                    }

                    if (trueDamage)
                    {
                        if (essence)
                        {
                            target.ReciveTrueDamageEssence(dmg, bf, q, v);
                        }
                        else //no test for ancient
                        {
                            target.ReciveTrueDamageAncient(dmg, bf, q, v);
                        }
                    }
                    else
                    {
                        target.ReciveNormalDamage(dmg, bf, q, v);
                    }

                    if (lifeLeech)
                    {
                        if (essence)
                        {
                            lifeLeeched += (dmg * 0.4f).ToInt();
                        }
                        else
                        {
                            lifeLeeched += (dmg * 0.8f).ToInt();
                        }
                    }
                    if (shieldLeech)
                    {
                        if (essence)
                        {
                            shieldLeeched += (dmg * 0.5f).ToInt();
                        }
                        else
                        {
                            shieldLeeched += (dmg * 1.0f).ToInt();
                        }
                    }
                }
            }
            #endregion
            #region Secondary targets
            ////do splash if needed
            if (!NetType.IsNullOrEmpty(q.SecondaryTargets))
            {
                bool splashBonus = (flags & (int)EActivatorBlocks.AOE) > 0;

                if (gray || !splashBonus)
                {
                    damage *= 0.5f;
                }
                else
                {
                    //this is !gray && AOE
                    if (essence)
                    {
                        damage *= 0.75f;
                    }
                    if (ancient)
                    {
                        damage *= 1f;
                    }
                }

                //ancient damage is 100% splash

                foreach (var v in q.SecondaryTargets.value)
                {
                    target = bf.ConvertPositionIDToCard(v);
                    if (target != null)
                    {
                        FInt dmg = damage;
                        if (poisonDamage && target.IsWounded())
                        {
                            if (essence)
                            {
                                dmg *= 1.35f;
                            }
                            else
                            {
                                dmg *= 1.6f;
                            }
                        }

                        if (trueDamage)
                        {
                            if (essence)
                            {
                                target.ReciveTrueDamageEssence(dmg, bf, q, v);
                            }
                            else //no test for ancient
                            {
                                target.ReciveTrueDamageAncient(dmg, bf, q, v);
                            }
                        }
                        else
                        {
                            target.ReciveNormalDamage(dmg, bf, q, v);
                        }

                        if (lifeLeech)
                        {
                            if (essence)
                            {
                                lifeLeeched += (dmg * 0.4f).ToInt();
                            }
                            else
                            {
                                lifeLeeched += (dmg * 0.8f).ToInt();
                            }
                        }
                        if (shieldLeech)
                        {
                            if (essence)
                            {
                                shieldLeeched += (dmg * 0.5f).ToInt();
                            }
                            else
                            {
                                shieldLeeched += (dmg * 1.0f).ToInt();
                            }
                        }
                    }
                }
            }
            #endregion

            #region Leech
            if (lifeLeeched > 0)
            {
                FInt max = owner.GetCA_MAX_HEALTH();
                FInt cur = owner.GetCA_HEALTH();
                if (cur < max)
                {
                    if (lifeLeeched > max - cur)
                    {
                        owner.SetCA_HEALTH(max);
                    }
                    else
                    {
                        owner.SetCA_HEALTH(cur + lifeLeeched);
                    }
                }
            }
            if (shieldLeeched > 0)
            {
                FInt cur = owner.GetCA_SHIELD();
                owner.SetCA_SHIELD(cur + shieldLeeched);
            }
            #endregion


            return(null);
        }